Where is SharePoint 2010 Service Pack 1? The world wonders.

Those of you who know your history will instantly recognize the title above as borrowed from the coded message of October 24, 1944 from Admiral Nimitz to Admiral Halsey, which originally read “Where is Task Force Thirty Four?” plus the padding phase “The world wonders” which was designed to confuse the Japanese.

In today’s world, it means that approximately a year after it first appeared, SharePoint 2010 still lacks an official Service Pack. The world wonders when it will show up.

Yes, there are numerous (at least five) “cumulative updates”, but I do wonder what kind of person has converted or is planning a conversion of any large production SharePoint 2007 installation to a production SharePoint 2010 installation without first waiting for the official Service Pack 1.

Having used Microsoft technology since 1987, I’ve learned (sometimes the hard way) to always wait for Service Pack 1 before committing to a new major version of anything produced by Microsoft. If you don’t believe me, ask anyone who jumped into SQL Server 7 before Service Pack 1.

Update 2011-05-28: Microsoft says that Service Pack 1 should arrive by then end of June 2011.

64-bit only requirement will dampen SharePoint 2010 uptake

I hope that I’m not the only person who is concerned that the 64-bit only restriction of SharePoint 2010 will put a serious crimp on people who learn new technology by installing it in VM’s on their laptop (or laptop + external drive).

In the current bad economy, I don’t know of many developers who are ditching (or can afford to ditch) their 32-bit laptops for 64-bit laptops just so they can be prepared to install and run SharePoint 2010 in a virtual machine when it makes its debut in October.

How are we supposed to learn SP 2010 at workshops and seminars or give demonstrations to users groups if we cannot do so on our existing 32-bit laptop investments? I believe there will be a serious decline in developers willing to learn SharePoint 2010 with such a high barrier to entry.

Sure, there will be virtual labs, but for me, installing and configuring SharePoint from scratch in a VM is one of the best ways to learn “What Mother [Microsoft] Didn’t Tell You About Installing And Running SharePoint 2010.” Using VMWare Workstation 6.5.2, I can use Snapshots to fork in multiple directions (e.g., WSS-only or WSS+MOSS).

I have plenty of RAM on my existing laptop (4GB) to run a 1024KB virtual machine consisting of Windows Server 2008 R1, SQL Server 2008 SP1, and SharePoint 2007 SP1, so why should I pitch that fairly new laptop in the trash and buy a new one just to be able to run SharePoint 2010 and Windows Server 2008 R2?

Initial Thoughts On SharePoint 2010 Workflow Development Using WF 4.0, Visual Studio 2010

After reviewing the workflow-related videos from PDC 2008 and the SharePoint-related videos about Visual Studio 2010 on Channel9, I’ve come to the initial conclusion that SharePoint 2007 was not the “real” SharePoint for developing real-world workflows.  By that I mean that SharePoint 2007 is merely a “revenue making place holder” for Microsoft until the real SharePoint (2010, a.k.a. 14) comes out, complete with “proper tooling” for creating real-world workflows.

Think about how many add-ons it takes to turn Visual Studio 2005 into a viable custom SharePoint workflow creator.  Visual Studio 2008 takes fewer add-ons, has improved debugger support and some deployment capabilities,  but still lacks built-in capabilities to perform fundamental “duties” like generating a WSP.  You still need add-on tools from CodePlex and other places to turn Visual Studio 2008 into a half decent custom workflow development tool.

Even the latest CTP for Visual Studio 2008 Extensions for WSS version 1.3 does not provide the new Package or Retract commands for any project based upon the Office 2007 SharePoint Sequential Workflow template.  I know.  I tried it and found out the hard way that the workflow project templates in Visual Studio 2008 SP1 are still second-class citizens when it comes to VSeWSS.  You still need WSPBuilder and other tools available from third parties to perform the things that Microsoft tools still won’t do.

Don’t you just love the “flying blind” approach to creating ASP.Net workflow task forms in Visual Studio 2005/2008?  May God have mercy on Microsoft if that is still the case in VS 2010.  Better yet, why not let me design MOSS 2010 workflow task forms using WPF, perhaps in combination with Expression Blend or a similar XAML-based GUI design tool?

It’s only in Visual Studio 2010, together with .Net 4.0, WF 4.0 and its underlying XAML do we see (at least from the videos I’ve watched and the MSDN Developer Conference I recently attended) what I call “professional grade” tools for developing complex custom SharePoint workflows.  You can read about some of the improvements in the January 2009 edition of MSDN Magazine.

The fact that the workflow engine and the Visual Studio 2010 workflow designer were completely rewritten should tell you a lot about how Microsoft regards the quality of the “developer experience” when designing, coding, and even unit testing a custom SharePoint 2007 workflow in Visual Studio 2005/2008.  I found myself spending more time “wrestling” with SharePoint, InfoPath 2007 and WF 3.x to overcome their short-comings rather than spending time on the business process that caused us to turn to custom SharePoint workflows in the first place.  Have you ever sprinkled extra Delay Activities as part of a loop in your custom workflows for the sole purpose of waiting for the workflow to serialize a workflow task and thus generate the needed ID value for the purposes of generating a hyperlink value?  With SharePoint 2010 workflows, all you have to do is issue a Persist command.  Where was this KISS principle two years ago?  Probable answer: not enough time and too much pressure to keep feeding the Microsoft “revenue machine”, which requires regular feeding a minimum of four times a year when those all important 10Q and 10K reports are reviewed on Wall Street.

As more and more information about the initial “tooling” for SharePoint 2010 workflow development becomes available, perhaps in the form of downloadable virtual machine images, I’ll be interested to see if other people come to the same conclusion about the viability of the “tooling” that was initially delivered by Microsoft for SharePoint 2007 workflow development (i.e., minus all the subsequent add-ons).

The short version of this article: Under VS 2005/2008, SharePoint 2007 workflow development was more of a “bolted on” (after the fact) experience, not the “built in” experience we were all looking for.

Now if I could only find a decent 64-bit laptop running Windows Server 2008 R2 (a.k.a. Windows 7 Server) for that next SharePoint development machine…oh, well, subject for another post.

Oh Where Oh Where Has My Little [DelayActivty] Gone, Oh Where Oh Where Can He Be?

Ever wonder where SharePoint keeps it’s “To Do List of things I should remember about currently running DelayActivities”?

Want to figure out if there are any “dead timer events” associated with any of your workflow DelayActivities that will never be delivered?

Here’s a piece of SQL that will dump out all the event deliveries that are pending or overdue.

Just connect to your SharePoint SQL Server database, adjust the name of the content database (if necessary) and run it. By default, the example dumps out entries from the last seven days. If you want to dump out the entire table, just comment out the AND in the example below (which helps explain the weird-looking 1 = 1 condition).

If DeliveryDate is more than 10-20 minutes behind (adjusted for UTC to your local time zone conversion), chances are good that your DelayActivity will never “pop” at the desired time.

The mystery is why, which only Microsoft can answer.

At least you now have a tool to help discover these “dead timers”.

Note: I convert the DateTime fields to ISO 8601 strings for easier viewing and importing into tools such as Microsoft Excel.

--
-- What DelayActivities were supposed to "pop" in the last few days?
USE [WSS_Content]
GO

SELECT Convert(varchar, GetDate(), 120) [CurrentDateTime]
      ,Convert(varchar, wf.[Created], 120) wf_CREATED
      ,Convert(varchar, swi.[Created], 120) Created
      ,Convert(varchar, swi.[DeliveryDate], 120) DeliveryDate
      ,swi.[InternalState]
      ,wf.[SiteId]
      ,wf.[Id]
      ,[Type]
      ,[ParentId]
      ,wf.[ItemId]
      ,wf.[ItemGuid]
      ,[BatchId]
      ,wf.[WebId]
      ,[UserId]
      ,[BinaryPayload]
      ,[TextPayload]
      ,[ProcessingId]
      ,[ProcessMachineId]
      ,[ProcessMachinePID]
  FROM [dbo].[Workflow] wf
  INNER JOIN [dbo].[ScheduledWorkItems] swi
  ON wf.Id = swi.Id
WHERE 1 = 1
  AND swi.[Created] > DateAdd(Day, -7, GetDate())
ORDER BY
	swi.[Created] DESC

Prevent Workflow Tasks From Being Deleted

Here’s a simple way to prevent users from deleting their assigned tasks (who would ever want to do that??).

Start by coding an ItemDeleting event receiver that cancels the attempt to delete the task.

If you want to drive your users crazy, you can just set the Status to CancelNoError.
Try it out first before you decide to go that route.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace SameNameSpaceAsTheRestOfYourWorkflow
{
    class PreventWorkflowTaskDelete : SPItemEventReceiver
    {
        ///
        /// Prevent a workflow task from being deleted.
        ///
        ///
        public override void ItemDeleting(SPItemEventProperties properties)
        {
            string errorMessage = string.Empty;
            try
            {
                errorMessage = string.Format("User {0} is not allowed to delete this Task.", properties.UserDisplayName);
            }
            catch
            {
                errorMessage = "You are not allowed to delete a task.";
            }
            finally
            {
                properties.ErrorMessage = errorMessage;
            }
            properties.Cancel = true;
            properties.Status = SPEventReceiverStatus.CancelWithError;
        }
    }
}

Next, when your workflow starts, call a method similar to the following to register the custom event receiver.
My example includes calls to my custom trace feature, so you’ll have to eliminate them before you can use this code.

#region PreventWorkflowTaskDeletion
        ///
        /// Prevent workflow tasks from being deleted by adding an event receiver that will cancel any
        /// attempt to delete a task from the workflow task list.
        ///
        private void PreventWorkflowTaskDeletion()
        {
            string methodName = MethodBase.GetCurrentMethod().Name;
            TraceEnter(methodName);

            string message = string.Empty;

            // prevent multiple ItemDeleting event receivers from being attached to the task list by removing any that may already exist.
            // NOTE: has the added benefit of replacing existing event receiver if we made a code change and redeployed recently.
            SPEventReceiverDefinitionCollection evrs = workflowProperties.Workflow.TaskList.EventReceivers;
            List ersToDelete = new List();
            foreach (SPEventReceiverDefinition er in evrs)
            {
                if (er.Type == SPEventReceiverType.ItemDeleting)
                {
                    // add the GUID index of the Event Receiver to the list of ones we need to get rid of
                    ersToDelete.Add(er.Id);
                }
            }

            // have to wait until outside of the above loop to delete; otherwise, you get this error:
            // System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
            foreach (Guid i in ersToDelete)
            {
                message = string.Format("Removed ItemDeleting event reciever with GUID of {0}", i.ToString());
                TraceInfo(message);
                evrs[i].Delete();
                workflowProperties.Workflow.TaskList.Update();
            }

            // Example: "Microsoft.Office.Samples.ECM.Workflow.ReplicatorContactSelectorSample, Version=3.0.0.0, Culture=neutral, PublicKeyToken=ec457ebe7d96977c";
            string assembly = Assembly.GetExecutingAssembly().FullName;
            message = string.Format("assembly = {0}", assembly);
            TraceInfo(message);

            // Example: "Microsoft.Office.Samples.ECM.Workflow.PreventWorkflowTaskDelete";
            string className = typeof(PreventWorkflowTaskDelete).FullName;
            message = string.Format("className = {0}", className);
            TraceInfo(message);

            // add the ItemDeleting event receiver to the current workflow task list
            try
            {
                workflowProperties.Workflow.TaskList.EventReceivers.Add(SPEventReceiverType.ItemDeleting, assembly, className);
                workflowProperties.Workflow.TaskList.Update();
                message = string.Format("Added ItemDeleting event");
            }
            catch (Exception ex)
            {
                message = string.Format("ERROR: Method {0} had an exception while attempting workflowProperties.Workflow.TaskList.EventReceivers.Add : {0}", ex.Message);
                TraceError(message);
                throw new SPException(message);
            }
            finally
            {
                TraceInfo(message);
            }

        }
        #endregion

In the above code, I was making frequent changes to the event receiver, thus the need to remove and re-add the event receiver each time.

PowerShell Script To Display Settings Critical To Proper Operation Of DelayActivity

Run this while logged on to your SharePoint server to see the current settings that may affect the proper operation of the DelayActivity in your workflows.  WARNING: Your auditor may not like the output from Job-Workflow-AutoClean. 

# PowerShell script to display various SharePoint internal settings that may affect DelayActivity event delivery
# Author: Fred Morrison
# Company: Exostar: The Trusted Workspace for Global Partner Networks - www.exostar.com
# Last Modification Date: 2008-04-02
# Name: SpGetSettings.ps1

# start by creating an alias for the stsadm command, allowing for 32-bit vs. 64-bit differences
Set-Alias -Name stsadm 	-Value $env:CommonProgramFiles'\Microsoft Shared\Web Server Extensions\12\BIN\STSADM.EXE'

#reference: http://technet.microsoft.com/en-us/library/cc424970.aspx
Write-Host "Job-Workflow"
stsadm -o getproperty -pn job-workflow -url http://localhost
Write-Host ""

#reference: http://technet.microsoft.com/en-us/library/cc262432.aspx
Write-Host "Job-Immdiate-Alerts"
stsadm -o getproperty -pn job-immediate-alerts -url http://localhost
Write-Host ""

#reference: http://technet.microsoft.com/en-us/library/cc424968.aspx
Write-Host "Job-Workflow-AutoClean"
stsadm -o getproperty -pn job-workflow-autoclean -url http://localhost
Write-Host ""

#reference: http://technet.microsoft.com/en-us/library/cc424943.aspx
Write-Host "Job-Workflow-Failover"
stsadm -o getproperty -pn job-workflow-failover -url http://localhost
Write-Host ""

#reference: http://technet.microsoft.com/en-us/library/cc263016.aspx
# NOTE: above documenation is in error - valid values seem to be "yes" or "no", not "a value between 1 and 500"
#       compare with Alerts-Maximum to see source of possible copy/paste error during creation of documentation.
Write-Host "Alerts-Limited"
stsadm -o getproperty -pn alerts-limited -url http://localhost
Write-Host ""

#reference: http://technet.microsoft.com/en-us/library/cc262842.aspx
Write-Host "Alerts-Maximum"
stsadm -o getproperty -pn alerts-maximum -url http://localhost
Write-Host ""

#reference: http://technet.microsoft.com/en-us/library/cc262968.aspx
Write-Host "Workflow-EventDelivery-Timeout"
stsadm -o getproperty -pn workflow-eventdelivery-timeout -url http://localhost
Write-Host ""

#reference http://technet.microsoft.com/en-us/library/cc261828.aspx
Write-Host "Workflow-EventDelivery-BatchSize"
stsadm -o getproperty -pn workflow-eventdelivery-batchsize -url http://localhost
Write-Host ""

#reference: http://technet.microsoft.com/en-us/library/cc262997.aspx
Write-Host "Workflow-EventDelivery-Throttle"
stsadm -o getproperty -pn workflow-eventdelivery-throttle -url http://localhost
Write-Host ""

Write-Host "Workflow-Cpu-Throttle (obsolete)"
stsadm -o getproperty -pn workflow-cpu-throttle -url http://localhost
Write-Host ""

Write-Host "Workflow-TimerJob-Cpu-Throttle (obsolete)"
stsadm -o getproperty -pn workflow-timerjob-cpu-throttle -url http://localhost
Write-Host ""

#reference: http://technet.microsoft.com/en-us/library/cc263471.aspx
Write-Host "Workitem-EventDelivery-BatchSize"
stsadm -o getproperty -pn workitem-eventdelivery-batchsize -url http://localhost
Write-Host ""

#reference: http://technet.microsoft.com/en-us/library/cc263141.aspx
Write-Host "Workitem-EventDelivery-Throttle"
stsadm -o getproperty -pn workitem-eventdelivery-throttle -url http://localhost

 

Workflow Task Due Dates – Give Them The Entire Last Day To Finish Their Task

Are you setting the DueDate property of your workflow tasks to a date that you pluck from an InfoPath 2007 workflow initialization form using logic similar to the following?

 // capture task due date from XSD-generated InfoPath form class module
ApproverDueDate = frmInit.ApproveDueDate;


// set up SPWorkflowProperties of the task
taskProps.StartDate = DateTime.Now;
taskProps.DueDate = ApproverDueDate;
taskProps.PercentComplete = 0.0f;

// later on, after a DelayActivity finishes waiting for the due date to arrive so it can check for incomplete tasks...
if {DateTime.Now > ApproverDueDate)
{
    // perform some actions that result in automatic task completion
}

Please realize that if ApproverDueDate is 2008-12-31, the task will be considered overdue at 2008-12-31 00:00:00, exactly one day less than what the task assignee might expect.  If you want to give the task assignee until the end of the day of the task due date to complete his task, you should change your code to something more like this:

// allow task to be completed up until 23:59:59 of the due date
ApproverDueDate = frmInit.ApproveDueDate.AddDays(1).AddSeconds(-1);

Keep the above in mind for overdue task reminder dates too!

Create a Discussion Thread on a Discussion Board with Author Override

So, there I was, trying to figure out how to create a new Discussion Thread on a SharePoint Discussion Board using the ‘feedback’ comments from a just-completed SharePoint task. 

It’s a great way to put all the comments from each completed workflow task in a SharePoint Discussion Board, providing an easy, ‘one-stop shopping’ view of all the feedback from all the workflow participants.  They can even engage in threaded conversations, if desired.

A quick Google search and I was off to the races using SPUtility.CreateNewDiscussion.  Should have been what I call a ‘quick victory’ in terms of time and code required.  It should have been even quicker because I wrote a small, one-step ‘proof of concept’ workflow using Visual Studio 2008 to verify how things should work.  VS 2008 streamlines the time needed to go from code to running a workflow by a factor of 4:1 compared to VS 2005.

Experience tells me there is a lot that ‘Mother [Microsoft] Never Told You About <<fill in subject>>…’, and this situation, of course, was no different. 

It seems that the ‘Author’ of the conversation thread cannot be specified via the CreateNewDicussion API.  It took some digging/experimentation (mostly try this, fail, try something else, fail) – the usual pattern in other words.  Even when I thought I had the solution, I got tripped up by the ‘freaky string format syntax’ required to change the ‘Author’ (and ‘Editor’ too as it turns out) to something other than the default of ‘System Account’ (or whatever account runs the SharePoint ‘engine’). 

So what exactly was it that ‘Mother [Microsoft] Never Told You About Specifying the Author of a SharePoint Discussion Thread’?  In code, it looks like the following, paying particular attention to the string.Format syntax. 

My question to anyone who might have some insight is this: was the person that dreamed up the freaky “;#” syntax needed to set the ‘Author’ and ‘Editor’ fields on drugs at the time he/she architected that part of SharePoint?  What happened to the KISS principle on this one?  Remember: Eschew Obfuscation!

/// <summary>
/// Creates a new Discussion thread on the specified Discussion Board, while associating the creator of the thread
/// to the specified person, not the default of 'System Account'
/// (or whatever account is running the SharePoint 'engine').
/// </summary>
/// <param name="ThreadCreator">SPUser object instance of the person who should become the thread Author.</param>
/// <param name="DiscussionBoard">SPList object instance of the Discussion Board a new Discussion Thread will be added too.</param>
/// <param name="Title">String value of the title of the new Discussion Thread.</param>
/// <param name="Body">String value of the body of the new Discussion Thread.</param>
/// <returns>SPListItem instance pointing to the newly created Discussion Thread</returns>
private SPListItem CreateDiscussionThread(SPUser ThreadCreator, SPList DiscussionBoard, string Title, string Body)
{
    SPListItem returnValue = SPUtility.CreateNewDiscussion(DiscussionBoard.Items, Title);
    returnValue[SPBuiltInFieldId.Body] = Body;
    string userInformation = string.Format("{0};#{1}", ThreadCreator.ID, ThreadCreator.Name);
    returnValue[SPBuiltInFieldId.Author] = userInformation;
    returnValue[SPBuiltInFieldId.Editor] = userInformation;
    returnValue.Update();
    return returnValue;
}