Microsoft Dynamics 365 Blog

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



[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);




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


public CrmDateTime InitialDate




return (CrmDateTime)base.GetValue(InitialDateProperty);




base.SetValue(InitialDateProperty, value);



[CrmOutput(“Upcoming Date”)] //Output Label

public CrmDateTime UpcomingDate




return (CrmDateTime)base.GetValue(UpcomingDateProperty);




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:


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

Michael Scott

We're always looking for feedback and would like to hear from you. Please head to the Dynamics 365 Community to start a discussion, ask questions, and tell us what you think!