Tuesday, February 24, 2009

MOSS 2007 - Workflow Approval Sets Modified By to "System Account"

NOTE that a better/working event handler Visual Studio 2008 project is provided in Part2. Download that project, build it, then use the Feature.xml, Elements.xml and UpdateColumns.dll from that project. Use the instructions shown here to perform the install on your SharePoint server.


I've been looking for a solution to this issue for a few days now. The basic problem is that when you use an Approval workflow (even a "No code" workflow) in MOSS 2007, as items are approved, the "Modified By" value of the item is set to "System Account" due to the way the Window Workflow service works. People who are using MOSS 2007 to track who last changed what then run into the issue of not being able to easily see who last touched the item as it goes through the approval process.

A good example of the problem is when you are trying use the Content Query Web Part to return a list of all items modified in the past two weeks. The report is fairly easy to configure, but when displaying the Modified By field again only shows "System Account" if the item is being processes by an approval workflow. So far, I havent found any columns that show what I would call the "Editor" without displaying the "System Account" during an approval.

Once I gave up on trying to find an existing column that would work, I started looking into custom code options. I found this blog article with more details about the actual problem and a possible work around using an event handler:

How to show task creator's & modifier's name instead of "System Account" in the Task Library

The idea here is to use a custom event handler on the Workflow Task List to capture the ItemAdded and ItemUpdated events on a task. We then try and override the Modified By column with the value of the user name of the person performing the action at the end of the event.

Unfortunately, the instructions are not very detailed. It took me about a day to figure out how to create and install a custom event handler using a feature. And testing changes to the handler can be even more tricky...sometimes you need to reset IIS to see your changes, so dont forget to do that when you install new event handler assemblies (I always do an iis reset when I uninstall a feature now, so that if I need to re-install it I know it wont be "cached" by iis).

Even after getting the event handler to execute, I still had no success with changing the Modified By to be the actual user account of the Editor, and I'm not sure exactly why it does not work. I'm going to spend another day or two testing this out to see if I missed something, and I'll be posting a complete example of how to install an event handler that overrides multiple item level events.





Here is my attempt at implementing the Event Handler described in the link above. The code compiles and seems to fire off, but it does not override the System Account as the Modified By as I would expect it to. I can, however, change the methods to ItemAdding and ItemUpdating and abort with error messages, that does seem to work. So I think I'm on the right track here, but perhaps these properties arent actually doing what I expect them to...

Feature.xml
The feature ID must be a unique GUID, be sure to generate a new GUID one before you deploy the feature.


<?xml version="1.0" encoding="utf-8"?>
<Feature Id="A3A53813-E68A-4EF8-9C4A-FB0A839A088A" 
    Title="UpdateColumns"
    Description="An event handler that catches ItemAdded and ItemUpdated events, then replaces the Modified By to match that of the editor to bypass the System User issue."
    Scope="Web"
    xmlns="http://schemas.microsoft.com/sharepoint/">
    <ElementManifests>
        <ElementManifest Location="Elements.xml"/>
    </ElementManifests>
</Feature>



Elements.xml
In order to allow your assembly to act on several types of events (in this case, the ItemAdded and ItemUpdated events), you'll need to add a new Receiver for each event you intend to capture in the Elements.xml file. you'll note that the "ListTemplateID" is set to 107, which is the internal ID for the sharepoint task list. Also, the PublicKeyToken must match that of your assembly. When you install the assembly to the server GAC, you can view the properties of the assembly there to find the PublicKeyToken.


<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Receivers ListTemplateId="107">
        <Receiver>
            <Name>UpdateColumns</Name>
            <Type>ItemAdded</Type>
            <SequenceNumber>20010</SequenceNumber>
            <Assembly>
                UpdateTaskColumns, Version=1.0.0.0, culture=neutral, PublicKeyToken=9c85e3ff7e12eba3
            </Assembly>
            <Class>UpdateTaskColumns.UpdateColumns</Class>
        </Receiver>
        <Receiver>
            <Name>UpdateColumns</Name>
            <Type>ItemUpdated</Type>
            <SequenceNumber>20011</SequenceNumber>
            <Assembly>
                UpdateTaskColumns, Version=1.0.0.0, culture=neutral, PublicKeyToken=9c85e3ff7e12eba3
            </Assembly>
            <Class>UpdateTaskColumns.UpdateColumns</Class>
        </Receiver>
    </Receivers>
</Elements>


UpdateColumns.cs
Use the "Class Library" project type in VS so that you can compile the code down to a .dll file. A reference to the Microsoft.SharePoint assembly is required to compile. If you dont have SharePoint installed on your local development machine, you can simply copy the Microsoft.SharePoint.dll file from your sharepoint server to your local project directory and add a reference to it from there. You'll also need to sign the assembly before you deploy it to the GAC.


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

namespace UpdateTaskColumns
{
    public class UpdateColumns : SPItemEventReceiver
    {
        public override void ItemAdded(SPItemEventProperties properties)
        {
            UpdateUserColumns(properties, false);
        }
        public override void ItemUpdated(SPItemEventProperties properties)
        {
            UpdateUserColumns(properties, true);
        }
        private void UpdateUserColumns(SPItemEventProperties properties, bool IsUpdate)
        {
            SPWeb oWeb = properties.OpenWeb();
            Guid oSourceListID = new Guid(properties.ListItem["Workflow List ID"].ToString());
            SPList oSourceList = oWeb.Lists.GetList(oSourceListID, true);
            SPListItem oSourceListItem = oSourceList.GetItemById(Convert.ToInt32(properties.ListItem["Workflow Item ID"]));
            if (IsUpdate)
                properties.ListItem["Editor"] = oSourceListItem["Editor"];
            else
            {
                properties.ListItem["Author"] = oSourceListItem["Editor"];
                properties.ListItem["Editor"] = oSourceListItem["Editor"];
            }
            properties.ListItem.Update();
        }
    }
}


Deploy Files

  1. Compile and copy the assembly to the server GAC

  2. Deploy Feature.xml and Elements.xml to the server's 12 hive features directory, under a new feature directory name. For example: "<12hive>\TEMPLATE\FEATURES\UpdateColumns\"

  3. Install the feature to the farm using the STSADM command: "stsadm.exe -o installfeature -filename UpdateColumns\Feature.xml"

  4. With the feature installed at the "Web" level, you can activate the feature on individual subsites in your web application. Browse to the site settings for the site you want to test this feature on, click "Site Features" and then click the "Activate" button for "UpdateColumns"



References

No comments: