Thursday 28 February 2008

New version of Content Deployment Wizard released

I'm happy to announce that development/testing of the next version of the SharePoint Content Deployment Wizard is now complete, and the new release has been uploaded to Codeplex. If you're not aware of the tool then Introducing the SharePoint Content Deployment Wizard is a good place to start. I'm calling this release "beta 2", which indicates the tool is by no means complete, but is significantly more mature than the original version. I'd encourage all of the 1400+ people (and counting) who downloaded the original in the past few weeks to get the latest version - the link is at the bottom of this post.

So let's run through the improvements:

  • Better support for large sites:

    The treeview of the site is no longer built in one hit - instead, portions of the site are retrieved as webs/lists are expanded. This can make a huge difference when working with large sites.

  • Support for WSS-only sites:

    The dependency on the MOSS assembly (Microsoft.SharePoint.Publishing) has now been removed, so imports/exports to WSS sites are now possible. The MOSS-specific functionality is only used if we are indeed in a MOSS environment - if you're interested, this is just related to honoring any publishing schedules (i.e. page expiry) from the source site on the target site.

  • Everything in one .exe:

    No separate WizardBase.dll any more.

  • Easier access to log files after import/export:

    Since it's highly important to analyze the log file for errors/warnings after an import/export, these now open automatically in notepad at the end of the operation. There's also a link in the tool to open the log file, so no more manual digging around the filesystem.

  • Auto-discovery of sites in 'Site URL' box:

    No need to re-enter the site URL each time - the sites available for selection in your SharePoint environment are automatically shown in the auto-complete entries for the textbox:

     AutoSiteDiscovery

  • Pretty icons in treeview :-)

    Easier visual distinction between objects in the tree:
     TreeViewImages

  • Logging via System.Diagnostics.Trace:

    In addition to the log file generated by the actual import/export operation, general operations in the tool are now also logged (separately) via System.Diagnostics. So if you have a problem using the tool, it should be easier to pinpoint the actual error - enable by adding a .config file with a TraceSwitch entry for "COB.SharePoint.Utilities.ContentDeploymentWizard" and appropriate TraceListener entries.


  • More reliability:

    Miscellaneous minor bug fixes, including removal of the somewhat psychedelic flickering as the confirmation screen is built.

In terms of issues reported in the first release, it seems that 95% of them have been related to the underlying Content Migration API provided by SharePoint rather than the actual Wizard - this means the issue would also likely surface if out-of-the-box Content Deployment (via Central Admin) or STSADM -export was used to deploy the content instead. This is in line with what I was expecting since in many ways the Wizard doesn't actually do that much - it just presents a nice UI (with the ability to cherry-pick exactly which items should be deployed) onto existing SharePoint code. Since there are some gotchas with the MS bits however, it does mean that understanding how their API works can help when using the Wizard. My recommended reading list for those wanting to know more would be:

The future

I've had great feedback so far (ranging from feature requests to "why the hell don't you charge for this thing?!" to offers of collaboration). In terms of features, it looks like incremental deployment is the most popular vote so far so I'm hoping to add this fairly soon. Another possibility is making the Wizard scriptable by refactoring the code into something more tiered, thus providing features like the ability to save configurations for import/export jobs and also allowing them to be hooked up to a task scheduler in some way. 

However, the more feedback the better and if you'd like to see particular functionality or have run into problems trying to use the tool I'm definitely keen to hear. Either leave a comment on the blog or over at the Codeplex site.

The download link
 

Go to www.codeplex.com/SPDeploymentWizard and go to the releases tab to download the latest version. Hope you find it useful.

Wednesday 20 February 2008

More workflow issues and resolutions

What I wanted to do today is run through is wrap up my series of workflow posts, by running through some of the issues I hit during development. If you're into workflow, you may have seen Rob Bogue's excellent 10 issues (and resolutions) for SharePoint+Workflow - this post is something of an extension to Rob's. It probably makes for rather dry reading, but will hopefully be useful to those Googling for specific problems.

  1. Issue:- Error message "System.Xml.XmlAttribute is not serializable" shown in workflow history list and workflow will complete before it is supposed to. In SharePoint log, will see "End of Stream encountered before parsing was completed” error.

    More information:- This is caused by trying to store a class which cannot be serialized as a member (e.g. private variable) of the workflow class. Furthermore, I also encountered this error when storing the class which represents the InfoPath form - interestingly this class will always definitely be serializable, but the error was happening nevertheless.

    Resolution:- Mark member as NonSerializable (so workflow does not attempt to serialize the class, and copy primitive data (e.g. strings, dates etc.) out of form class and into individual field-level variables. It might also be possible to mark the ‘AnyAttr’ member of the generated class as NonSerializable instead.

  2. Issue:- InfoPath forms do not get updated upon solution deployment - changes are not reflected.

    More information:- This happened when I initially started development. I noticed that if I separately deactivated/uninstalled the workflow Feature, the InfoPath forms would then update successfully. This pointed me to an issue with the PostBuildActions.bat script.

    Resolution:- Modify PostBuildActions.bat to use the -force parameter when deactivating/uninstalling the Feature.

  3. Issue:- Error message "The e-mail message cannot be sent. Make sure the outgoing e-mail settings for the server are configured correctly." is shown in the workflow history list. E-mail configuration is known to be good (e.g. alerts are working).

    More information:- I suspect there are several reasons why this may happen, but for me the problem was how I was using the SendEmail activity. When I was setting properties on the SendEmail variable the workflow designer had added to my class for me, I had the error.

    Resolution:- The best way I found to deal with this was to set properties on the object which is passed to the SendEmail's 'MethodInvoking' code. You'll need to cast to SendEmail before you can do this.

  4. Issue:- The TaskID for a CreateTask activity does not get populated. The value defaults to -1 instead.

    More information:- This will be an issue in several scenarios, one example is when building the URL for a task (e.g. to include in an e-mail to a user) with code similar to Paul Hunt's code shown at http://suguk.org/forums/thread/4978.aspx.

    Resolution:- The TaskID will only be populated if there is a place to store the value. Ensure the TaskID property on your CreateTask activity is bound to a variable.

  5. Issue:- Error message "Correlation value on declaration 'x' is already initialized" message shown in SharePoint log when a task executes for the second time in a workflow.

    More information:- This scenario can happen when a CreateTask runs for a 2nd time due to an approver rejecting something in the workflow.

    Resolution:- The 'OwnerActivtyName' of the task's correlation token is incorrectly set. In a state-machine workflow, it should refer to the particular state, not the overall workflow.

  6. Issue:- Unexplained errors when some part of an otherwise functioning workflow is commented out in the designer (e.g. 'Failed on start').

    More information:- You've commented out one or more activities in your workflow (using right-click > Disable), but observe strange behaviour and/or errors. This is actually because, in contrast to commenting out source code, the code is actually still compiled into the assembly. Hence if there is an error with say, a correlation token being incorrectly set, this will still cause a problem in your workflow.

    Resolution:- Fix problem or remove faulting activity/activities completely from workflow.

For completeness, some other common issues which are well documented in other sources are:

  • 'Failed on start' - this often caused by a problem loading the workflow assembly. Check the PostBuildActions.bat script (or whatever you are using) has properly deployed the assembly to the GAC, and all the assembly attributes (version, public key token etc.) match those specifed in the Feature files.
  • 'SPException: This task is currently locked by a running workflow and cannot be edited' message. This is documented by Rob Bogue and others, but I'd also add this can occur as a knock-on effect of another problem. If an earlier exception has occurred whilst trying to process a task, the workflow will have locked the task on the first run, meaning that subsequent attempts by a user to respond to the task will result in this error. Other than fixing the problem at source, I haven't found an elegant way of dealing with this.

So hopefully that's of some use to workflow developers. If you haven't seen it already, there's a lot more goodness (sample code, tips and tricks etc.) in my workflow resources pack from the workflow deep-dive presentation I did at the UK SharePoint user group earlier this year.

Happy coding!

Sunday 3 February 2008

InvokeWorkflow - child workflows not supported in SharePoint?

Our client through an interesting curve ball at us midway through my workflow project. We were probably 70% code complete, when it was realised the design had no provision for a certain scenario. The workflow is triggered by a user completing an InfoPath form - this has many input controls to collect data, and several of them are dropdowns which dictate the particular path through the workflow which occurs. One such dropdown looks something like:

Type:

  • type 1
  • type 2
  • both

The value selected is evaluated by IfElse conditions in the workflow - if "type 1" is chosen, we go down a particular branch, if "type 2", a different branch and so on. Now for this particular dropdown, we realised we hadn't specified what should happen for the "both" condition (that's agile for you!). The new requirement which arose was that notifications and tasks should go to to two teams (i.e. both of them), and after further discussion we decided it would be appropriate to kick off two separate workflows, one for each team.

Most workflow frameworks can deal with this by using 'child' workflows, and Workflow Foundation is no different. After a couple of hours refactoring the implementation to use this approach, my proof-of-concept looked like:

ParentWorkflow

So whilst the 'InvokeWorkflow' activities are closed (i.e. the child workflows aren't expanded), you can hopefully see that if we go down the branch on the left, we launch one child workflow, but in the branch on the right (for "both"), we launch two child workflows in parallel.

The problem

I tried a couple of things, the image above shows using a sequential parent workflow (though the child workflow is state-machine), but couldn't get this to work. Using the debugger I could see that the child workflows were being initialized - I could step through InitializeComponent(), but breakpoints in 'OnWorkflowActivated' were never hit - the event wasn't raised. The SharePoint log showed:

System.Workflow.Activities.EventDeliveryFailedException: Event "OnWorkflowActivated" on interface type "Microsoft.SharePoint.Workflow.ISharePointService" for instance id "67c4929a-9280-4b8b-9a4c-a1b85b04267c" cannot be delivered. ---> System.InvalidOperationException: Event Queue operation failed with MessageQueueErrorCode QueueNotFound for queue 'Message Properties Interface Type:Microsoft.SharePoint.Workflow.ISharePointService Method Name:OnWorkflowActivated

So I became suspicious of whether it's possible to use InvokeWorkflow in a SharePoint workflow. Microsoft haven't yet got back to me on this, and the most detailed SharePoint workflow book I'm aware of (Workflow in the 2007 Microsoft Office System by David Mann) does not discuss InvokeWorkflow.

Time for some lateral thinking.

The solution

We know that we will definitely have two workflows if two completed InfoPath forms are added to the document library, so I asked the client what they thought of getting the system to split a "both" request into two separate items - this would mean having two separate InfoPath forms in the list. The answer was "actually that's probably better for the users come to think of it", so the solution I came up with was:

  • a list event receiver which executes before the workflow - this does the following:
    - opens the binary contents of the InfoPath form as a string, and modifies the dropdown value from "both" to "type 1"
    - creates a duplicate of the original file in the same library, but modifies the dropdown value here from "both" to "type 2"
  • running some ad-hoc code to reorder the event receivers on the list - we need to split the item before workflow starts (so we get two workflows) but SharePoint adds the SPWorkflowAutostartEventReceiver at sequence of 1 so it executes first. This code switches the event receiver sequence so our pre-processing happens first.

On the last point, I considered that the SharePoint team may have set workflow to execute first for a reason, but then again it's just another event receiver and SharePoint does not prevent this reordering (in the same way it does with some other changes, such as deleting the welcome page from a web for example). In any case, we've tested pretty extensively and the solution hasn't missed a beat.

In case it's useful, the following code can be used to duplicate and modify a file - this could be used in a list receiver (as in my case) or in ad-hoc code. I used simple string manipulation to perform the modification since InfoPath forms are just XML, and also because using XML-handling methods caused issues with the 'proprietary' InfoPath declarations. Simply replace the string replacement line with your own processing.

   1: private string getFileContentsAsString(SPFile file)
   2: {
   3:     byte[] aFileBytes = file.OpenBinary();
   4:     string sContents = Encoding.UTF8.GetString(aFileBytes);
   5:  
   6:     return sContents;
   7: }
   8:  
   9: private void duplicateFile(SPListItem originalListItem, string sNewItemUrl, 
  10:     bool bDeleteOriginal)
  11: {
  12:     SPFile originalFile = originalListItem.File;
  13:     string sOriginalFileContents = getFileContentsAsString(originalFile);
  14:  
  15:     // TODO: replace this line with your own processing..
  16:     string sNewFileContents = sOriginalFileContents.Replace("oldValue", "newValue");
  17:     
  18:     byte[] aNewFileBytes = Encoding.UTF8.GetBytes(sNewFileContents);
  19:  
  20:     // we effectively need to add a new file for this operation, and optionally delete 
  21:     // the original..
  22:     SPFile newFile = originalListItem.ParentList.RootFolder.Files.Add(sNewItemUrl, aNewFileBytes,
  23:                                                                       originalFile.Properties);
  24:     newFile.Update();
  25:     
  26:     if (bDeleteOriginal)
  27:     {
  28:         originalListItem.Delete();
  29:     }
  30: }



P.S. If anyone is using InvokeWorkflow in a SharePoint workflow, leave a comment :-)