Saturday, March 27, 2010

Compilation failed. Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.

If you've recently upgraded FIM to RTM and you're getting the following error message when you compile your custom activities, then you probably need to copy the new Microsoft.IdentityManagement libraries out of the GAC.

"Compilation failed. Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information."

copy /y C:\Windows\assembly\GAC_MSIL\Microsoft.IdentityManagement.WFExtensionInterfaces\4.0.2592.0__31bf3856ad364e35\Microsoft.IdentityManagement.WFExtensionInterfaces.dll .

copy /y C:\Windows\assembly\GAC_MSIL\Microsoft.IdentityManagement.Activities\4.0.2592.0__31bf3856ad364e35\Microsoft.IdentityManagement.Activities.dll .

copy /y C:\Windows\assembly\GAC_MSIL\Microsoft.IdentityManagement.WebUI.Controls\4.0.2592.0__31bf3856ad364e35\Microsoft.IdentityManagement.WebUI.Controls.dll .

Monday, March 22, 2010

Query/enumerate all declarative workflow templates in an SPWeb and programmatically read the XOML files

It took me all day to figure this one out. You'd think something like this could be done through the object model, but it's not available in WSS. Here's a complete copy of my console app, with all trial/error code left in. I hope this helps someone out there!

Edit: Sorry, I left out some details. What can I say; I was a little weary after a grueling battle with Sharepoint. Anyway, the workflows I'm talking about here are declarative Sharepoint workflows, meaning that you assemble them in Sharepoint Designer.

Also, I was able to read the XOML files through the object model, but I wouldn't call it the most direct route. You'd think that you could read the workflow templates through the object model, but they don't show up in SPWeb.WorkflowTemplates, SPWorkflowAssociation.BaseTemplate is null (probably because it's declarative), and there is no Workflow.asmx service in WSS (not sure if that would even suffice).

So, what I ended up doing was getting the workflow name from the SPWorkflowAssociation (from the SPList), and then assembling a URL in the form of:

/Workflows/wfName/wfName.xoml

Then, I get a handle on the XOML as an SPFile, and from there I can read it into an XmlDocument.


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Ensynch;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;

namespace EnumerateSPWorkflowTemplates
{
class Program
{
private static string webUrl =
"http://ens-ilm01/sites/devsandbox/spworkflow/";

static void Main(string[] args)
{
try
{
using (SPSite site = new SPSite(webUrl))
using (SPWeb web = site.OpenWeb())
{
foreach (SPWorkflowTemplate wf in web.WorkflowTemplates)
{
Console.WriteLine(EnsynchTools.PrintHeading(
"Workflow Template: " + wf.Name));
//Console.WriteLine(wf.Xml);
writeXml(wf.Xml);
}
foreach (SPList list in web.Lists)
{
foreach (SPWorkflowAssociation association in list.WorkflowAssociations)
{
//SPWorkflowTemplate wf = association.BaseTemplate;
//if (wf != null)
//{
Console.WriteLine(EnsynchTools.PrintHeading(
"Workflow Association: " + association.Name));
//Console.WriteLine(association.SoapXml);
writeXml(association.SoapXml);
readWorkflowTemplate(association);
//}
}
}
crawlFolders(web, "Workflows");
}
}
catch (Exception exc)
{
Console.WriteLine(EnsynchTools.ExceptionDetails(exc));
}

Console.Write("Press enter to exit...");
Console.ReadLine();
}

private static void writeXml(string xml)
{
XmlDocument doc = new XmlDocument();
doc.Load(new StringReader(xml));
writeXml(doc);
}

private static void writeXml(XmlDocument doc)
{
doc.Save(Console.Out);
Console.WriteLine("\r\n");
}

private static void readWorkflowTemplate(
SPWorkflowAssociation association)
{
XmlDocument doc = new XmlDocument();
doc.Load(new StringReader(association.SoapXml));
XmlNode node = doc.SelectSingleNode("/WorkflowTemplate");
// Assemble the URL from the workflow name.
XmlAttribute attribute = node.Attributes["Name"];
string wfName = attribute.Value.Replace(" ", "%20");
string webRelativeFolder = "Workflows/" + wfName;
string xomlFileName = wfName + ".xoml";
string xomlUrl = webUrl + webRelativeFolder + "/" + xomlFileName;
try
{
Console.WriteLine("Trying to access " + xomlUrl);

SPFolder wfFolder = association.ParentWeb.GetFolder(
webRelativeFolder);
SPFile xomlFile = wfFolder.Files[xomlFileName];
Console.WriteLine("Found file: " + xomlFile.Url);

//System.Net.WebClient oWebClient = new System.Net.WebClient();
//oWebClient.Credentials = new System.Net.NetworkCredential (
// "username","password","domain");
//String sResponseData = System.Text.Encoding.ASCII.GetString(
// oWebClient.DownloadData(xomlUrl));

doc = new XmlDocument();
//doc.Load(new StringReader(sResponseData));
using (Stream xomlStream = xomlFile.OpenBinaryStream())
{
doc.Load(xomlStream);
}
writeXml(doc);
Console.WriteLine("\r\n");
}
catch (Exception exc)
{
Console.WriteLine(exc.Message);
}
}

private static void crawlFolders(SPWeb web, string subFolder)
{
SPFolder rootFolder = web.GetFolder(subFolder);
crawlFolders(rootFolder);
}

private static void crawlFolders(SPFolder folder)
{
Console.WriteLine("Crawling: " + folder.Url);
foreach (SPFile file in folder.Files)
{
Console.WriteLine("- " + file.Name);
}
// Recursively count SPFiles in SPFolders
foreach (SPFolder subfolder in folder.SubFolders)
{
crawlFolders(subfolder);
}
}

}
}