Part 2: Happy Birthday via Workflow using Custom Workflow Activity


In Part 1 of this two part blog series, I discussed how to Send E-mail to contacts on annual basis, based on a Custom Attribute that is updated with Client-Side scripting. There is another way to accomplish the same goal using a Custom Workflow Activity and a second Workflow.

Option #2: Custom Workflow Activity

The following steps need to be completed:

1. Create and Register the custom workflow activity

2. Create a Workflow that uses the activity to update the custom attribute

Step #1: Creating the Activity

Note: Windows Workflow Foundations (WinWF) must be installed on your machine (it is included with .NET Framework 3.5 and is available as an extension to.NET Framework 3.0). I am using Visual Studio 2005 to build the activity.

Create the Visual Studio project with the following references (CRM assemblies can be found in the GAC of the Platform Server role machine):

  • Microsoft.Crm.Sdk
  • Microsoft.Crm.SdkTypeProxy
  • System
  • System.Workflow.Activities
  • System.Workflow.ComponentModel
  • System.Workflow.Runtime

Copy and paste the code that follows into a new C# class.

using System;

using System.Workflow.ComponentModel;

using System.Workflow.ComponentModel.Design;

using System.Workflow.ComponentModel.Compiler;

using System.Workflow.Activities;

using Microsoft.Crm.Workflow;

using Microsoft.Crm.Sdk;

using Microsoft.Crm.SdkTypeProxy;

using Microsoft.Crm.Sdk.Query;

namespace ExampleActivities

{

[PersistOnClose]

[CrmWorkflowActivity("Calculate Next Occurrence", "Examples")]

public partial class CalculateNextOccurrence : SequenceActivity

{

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)

{

DateTime today = DateTime.Today;

DateTime initialDate = this.InitialDate.UserTime;

DateTime upcomingDate = DetermineDateForYear(initialDate, today.Year);

if (upcomingDate.Date <= today)

{

upcomingDate = DetermineDateForYear(initialDate, today.Year + 1);

}

this.UpcomingDate = CrmDateTime.FromUser(upcomingDate);

return ActivityExecutionStatus.Closed;

}

private DateTime DetermineDateForYear(DateTime date, int year)

{

if (date.Month == 2 && date.Day == 29 && !DateTime.IsLeapYear(year))

{

return new DateTime(year, 3, 1);

}

else

{

return new DateTime(year, date.Month, date.Day);

}

}

//Define the dependency properties. CRM requires dependency properties used.

public static DependencyProperty InitialDateProperty = DependencyProperty.Register("InitialDate", typeof(CrmDateTime), typeof(CalculateNextOccurrence));

public static DependencyProperty UpcomingDateProperty = DependencyProperty.Register("UpcomingDate", typeof(CrmDateTime), typeof(CalculateNextOccurrence));

[CrmInput("Initial Date")] //Input Label

[ValidationOption(ValidationOption.Required)]

public CrmDateTime InitialDate

{

get

{

return (CrmDateTime)base.GetValue(InitialDateProperty);

}

set

{

base.SetValue(InitialDateProperty, value);

}

}

[CrmOutput("Upcoming Date")] //Output Label

public CrmDateTime UpcomingDate

{

get

{

return (CrmDateTime)base.GetValue(UpcomingDateProperty);

}

set

{

base.SetValue(UpcomingDateProperty, value);

}

}

}

}

Explanation of the Code
The activity has one input parameter, Initial Date, which is set to the Birthday. The activity’s output parameter, Upcoming Date, can be used to update the Upcoming Birthday field.

The actual date calculation has been placed in a helper method, DetermineDateForYear. If we tried to set the Upcoming Birthday to be February 29th in a year that was not a leap year, an exception will be thrown. To avoid that issue, if February 29th is encountered in a non-leap year, March 1st is used instead.

The Activity’s Execute method utilizes this helper method to determine the date for a given year.

NOTE: CRM 4.0 stores all dates and times in Universal Time (UTC) within CRM 4.0. The date / time is converted to the current user’s time zone when it is retrieved and converted to UTC when it is updated. Any calculations that require a date / time to be at the same moment globally (e.g. an event occurring at 6:00 PM in Los Angeles would occur at 2:00 AM in London) should perform calculations using Universal Time.

Date Only fields (such as Birthday) are stored in the database as DateTime fields, which means that they are subject to Time Zone conversions. Initially, the activity used Universal Time, but this resulted in an incorrect date in certain time zones. To get around this issue, the activity calculates the upcoming birthday using User Time.

Step #2: Register the Assembly

Register the assembly as a Workflow Activity in the CRM deployment. If you want to debug the assembly, place the Symbols file (.pdb file) in the Server\bin\assembly folder of your CRM installation folder.

Option #2 – Step #3: Create the Workflow

This workflow will manage all updates to the Upcoming Birthday field.

1. Create a Workflow for Contact with these triggers:
    - On demand
    - Record is created
    - Record attributes change
– should trigger on updates to the Birthday attribute

2. Set the scope of the workflow as appropriate

3. The Workflow should have the following structure:

  • Check if Contact.Birthday has a value, then

          - Calculate Upcoming Birthday using the Custom activity with Initial Date set to the Birthday attribute

          - Update Contact.Upcoming Birthday to be the output parameter

  • Otherwise Clear Contact.Upcoming Birthday’s value

How do I do that?

Follow these steps:

1. Create a new Workflow for the Contact entity (click Settings, Workflows, and create a new Workflow). The following Workflow triggers (events or actions that will start the Workflow) should be selected:

  • a. On demandclip_image001 will appear on the Contact form and grid. This button allows the Workflow to be applied manually. If there are existing contacts that have not yet had their Upcoming Birthday set, you can apply the Workflow using this button.
  • b. Record is created – Workflow should run when the Contact is initially created.
  • c. Record attributes change – Runs when the Birthday attribute is changed.
    To set the attribute, click Select and check the Birthday checkbox in the list of attributes.

2. Set the Scope of the Workflow to the appropriate level (does not affect the On Demand trigger). The scope should match the other Workflow (discussed in the original blog post).

a. User: Workflow will only trigger on records with same as owner as the Workflow.

b. Business Unit: Workflow will trigger on records owned by any user in the same Business Unit as the owner of the Workflow.

c. Parent: Child Business Units: Workflow will trigger on records owned by any user in the same Business Unit (and any child Business Units) as the owner of the Workflow.

d. Organization: Workflow will trigger on records owned by any user.

3. Add a Check Condition and configure it.

a. Select Contact from the entity list (first drop-down list).

b. Select Birthday from the attribute list (second drop-down list).

c. Select Contains Data from the operator list (third drop-down list).

4. Add the custom activity to the Check Condition branch.

a. Click Select this row and click Add step

b. Select Examples from the Add Steps menu and click Calculate Next Occurrence.

5. Configure the custom activity with Contact.Birthday as a Dynamic Expression.

a. Click Set Properties to configure the step

b. Select the Initial Date field

c. In the Form Assistant, select Birthday from the attribute list (second drop-down list under Look for).

d. Click the Add button to add it to the list of attributes that will be used in the Dynamic Expression.

e. Click OK to add the Dynamic Expression

f. Save the configuration

6. Add an Update Record step to the Workflow (should follow the Custom Activity step) and configure it.

a. Select the Additional Fields tab
This tab contains attributes that are not shown on the regular form (such as Upcoming Birthday).

b. In the Form Assistant, select Calculate Next Occurrence from the entity list (first drop-down list under Look for).

c. Select Upcoming Date from the attribute list (second drop-down list under Look for).

d. Click Add to add to the list of attributes

e. Click OK to add the Dynamic Expression

f. Save the configuration

7. Add an Otherwise step.
Sometimes adding this step can be a bit tricky. In order to select the item from the menu, you need to click in the row that contains the condition (If Contact:Birthday contains data, then).

8. Add an Update Record step to the Otherwise branch and configure it

a. In the Additional Fields tab, select the Upcoming Birthday field

b. In the Form Assistant, select Clear from the Operator drop-down list.

c. Save the configuration

Your Workflow should look similar to this:

clip_image003

9. Publish the Workflow. Don’t forget to run this Workflow on any contacts that already exist.

Michael Scott

Comments (12)

  1. Did you see this post at blogs.msdn.com

  2. 0bscur3 says:

    Trying to figure out the register element using the pluging delveloer sdk. I can’t get the server.xml quite right. Can you help with an example.

    thanks

  3. Michael says:

    Please elaborate on Step #2: Register the Assembly.  I am using the Plugin Registration Tool and get an error when trying to register the assembly.

    Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly ‘Microsoft.Crm.Sdk’…

    Why is the tool looking for Microsoft.Crm.Sdk, when I pointed it to the assembly I just compiled?  Also, that dll is located in the same folder.

  4. Michael Scott says:

    I apoligize for being vague in Step #2. This blog was posted before the Plugin Registration Tool was published.

    The tool is loading the assembly into memory (which will look at the references of the assembly as well). Since Microsoft.Crm.Sdk.dll is referenced by the Custom Workflow Activity (and the tool does not already have it loaded for its own uses), it will attempt to load it as well. Since the DLL does not exist, it fails.

    1) Place Microsoft.Crm.Sdk.dll in the same directory as the Custom Workflow Activity.

    2) Add reference to Microsoft.Crm.Sdk.dll to Plugin Registration Tool project and place the DLL in the same place as the executable.

  5. Casey says:

    Followed the steps – no errors – but Custom Activity (Examples: Calculate Next Occurrence) doesn’t appear under Add Step.

  6. Sergio says:

    How can I register the dll as an activity workflow?

    I’m using the plugindeveloper tool as the sdk says but it doesn’t appear in the configuration workflow window.

  7. Marcel says:

    Hi, in the SDK-4.03 it says, that we have to use the plugin developer tool instead of the plugin registration tool with the nice gui

    can we use both`?

    Marcel

  8. Michael Scott says:

    You only need to use the Plugin Registration Tool (with the nice UI) to register Workflow Activities.

  9. Manikandan says:

    Hi,

    Can you explain the steps which should be taken in Visual studio 2005. which workflow template I have to select in Visual studio? I have selected "Empty workflow Project" and I have stored "PDB" file in the location "Serverassembley". But I could not see the "Example – Next Occurance" in the "Add step" of work flow.

    Please help us to resolve this issue.

    Thank you

  10. Viktor says:

    Hello everyone!

    I’m getting the following error from the Plugin Reg. Tool, while trying to register (load assembly) a simple workflow activity:

    Unhandled Exception: System.IO.InvalidDataException: Assembly name CRM Case Resolution Workflow, Version=1.0.0.0, Culture=neutral, PublicKeyToken=debc589651e5488b cannot be parsed.

    Does anyone have an idea what might be the problem? It’s a simple workflow example from the book Working with CRM 4.0.

    Thanks,

    Viktor

  11. GavinGan says:

    Happy Birthday via Workflow using Custom Workflow Activity

Skip to main content