Cancelling save event based on the result of async operation

EDIT (20/06/2021): OnSave can natively handle async So, this post is now obsolete.

When you want to cancel a save event in CRM Dynamics 365 Customer Engagement, you use “preventDefault()”  to block the save operation. This works when you block the operation based on the information that is currently on the form/page, but it does not work, if you want to block the save based on the result of an async operation.

In this contrived example, I would like to block the save of the current form, if there exists an user with “homephone” field set to 12345. The async operation is performed by “retrieveMultipleRecords” which returns a Promise.

The code below does not work>{
		console.log(`DataXml OnSave: ${}`);
		if(x.entities.some(x=>x.homephone == '12345')){
			console.log('User with homephone 12345 exists. Save blocked.');


Notice the the save event completed and form’s load event fired even though preventDefault ran. The “jobtitle” field that I modified also succeeded, when I expected it to not succeed.

Async Save block does not work

In order to block the save, you’ll have to restructure the code little differently, like the one below. Block save before async operation and explicitly call save, when your criteria for save is met and use closure variable to keep track of whether to save or not.

Working code>{
	let isSave = false;
	var uiClone = parent.jQuery.extend(true, {}, Xrm.Page.ui);
	var entityClone = parent.jQuery.extend(true, {},;

	var closeHandler = ()=>{
		console.log('local. close blocked.');

	var saveHandler = (ev)=>{
			console.log('local. save blocked.');
				isSave = !x.entities.some(x=>x.homephone == '12345');
				if(isSave){ =;
					Xrm.Page.ui.close = uiClone.close;
					if((typeof ev === 'string' && ev === 'saveandclose') ||
						(ev.getEventArgs && ev.getEventArgs() && ev.getEventArgs().getSaveMode() === 2)){
					console.log('User with homephone 12345 exists. Save blocked.');

	return (e)=>{
		var eventArgs = e.getEventArgs();
		console.log(`DataXml OnSave: ${}`);
		console.log(`Save Mode: ${eventArgs.getSaveMode()}`);
		if(isSave) {
			console.log('proceed to save'); =;
			Xrm.Page.ui.close = uiClone.close;
		else{ = saveHandler;
			Xrm.Page.ui.close = closeHandler;
			if(eventArgs.getSaveMode() !== 2){


Console Log Save Blocked

I have tested this only in Chrome on Dynamics 365 Online v9. Hope this is useful.

Troubleshooting Business Rules

Business Rules is composed of two components: a client side JavaScript and a server side workflow. You can use these queries to find out the details about this:

LinqPad – Query and result


FetchXml Builder – Query and result



One gotcha with the business rule is that its behavior will deviate, if a field that is required by the business rule is removed on the form. I will demonstrate this, with the business rule below.


This business rule sets the “Contracting Unit” to required, if Order Type is “Work Based”. Below are the screenshots of the form in two different scenarios:

With “Order Type” present on the form


Without “Order Type” present on the form

Without Order Type.png

As you can see, “Contracting Unit” is set to required, only if the “Order Type” field is present on the form, even if the value of “Order Type” is “Work based”. At present, there seems to no check in the form customisation area to prevent a field from being removed, if is required by a business rule. This is how the “Contracting Unit is required” business rule, gets translated into JavaScript.


When you debug this using Chrome Dev Tools, you can easily see why the “Contracting Unit” field is not being set to required.


To assist developers who are troubleshooting why a business rule is not working, I have developed this simple script to run in the DevTools console, that lists the fields that are required by the Business Rules, but are not present in the form. This has to be run in the context of ClientApiWrapper IFrame.

let formAttribs = Xrm.Page.getAttribute().map(a=>a.getName()); Object.keys(Mscrm.BusinessRulesScript.AttributesOnChangeHandlers).filter(x=>!formAttribs.includes(x))

Here is a sample output of this script.


It is saying that “Order Type” should be present in the form, as it is required by a Business Rule that is running on the form. It uses an unsupported internal method to identify this information, and so I recommend that it be used in devtools console only.

Further Reading:

Understanding Process Triggers and Business Rule internals

Gotcha: DateTimeKind and FetchXml

I was going through the FetchXML schema and found these two interesting attributes. The first one is “utc-offset” on the “fetch” node.


The other one is “usertimezone” on the “attribute” node.


Both these attributes give an impression, that it is possible to return datetime attributes in a different timezone instead of UTC, but in reality they don’t seem to do anything.

The only way to return datetime in the user’s local timezone, is by using the deprecated ExecuteFetchRequest. I will demonstrate this with XrmToolBox. This is my simple fetch query.


Below is the raw fetch result


Below is the table grid result


I execute the FetchXML using the FetchXML Builder tool. As you can see, the datetime values are different. This is because when you use “RetrieveMultiple” to execute the fetch query, the datetime returned is always UTC and has to be converted to the local timezone. ExecuteFetchRequest is now deprecated and RetrieveMultiple is the recommended way to execute fetchxml. These two points really puzzle me:

  1. Why user-timezone and utc-offset don’t seem to do anything. Are they internally used?
  2. Why MS decided to change the datetime behaviour

So, this is a good point to remember when you are executing fetchxml using “FetchXML Tester” or have set the Result view to Raw fetch result in “FetchXML Builder” or using a mixture of ExecuteFetchRequest and RetrieveMultiple in your code.


  1. ExecuteFetchRequest class
  2. XrmToolBox Issue #326


Issues in debugging custom workflow assemblies

Three months ago, I wrote a post about profiling workflows with custom activity step -> Debugging custom workflow assemblies

There were couple of comments in the post, about it not working as expected due to

  1. Different version of CRM and/or
  2. Different version of Plugin Registration tool

I will now give you the steps to get this working.

  1. Install LINQPad ->
  2. Install CRM Driver for LINQPad ->
  3. After creating a connection to your CRM instance, run this LINQ query
    XNamespace mxswa = "{clr-namespace:Microsoft.Xrm.Sdk.Workflow.Activities;assembly=Microsoft.Xrm.Sdk.Workflow, Version=, Culture=neutral, PublicKeyToken=31bf3856ad364e35}";
    var query = from w in WorkflowSet.AsEnumerable()
    w.FormattedValues["type"] == "Definition"
    && w.IsCrmUIWorkflow.GetValueOrDefault()
    orderby w.ModifiedOn descending
    select new {
    WorkflowVersion = XElement.Parse(w.Xaml).Attributes().FirstOrDefault(a => a.Name.LocalName == "mxswa").Value.Split(';')[1],
    CustomSteps = from a in XElement.Parse(w.Xaml).Descendants($"{mxswa}ActivityReference")
    			   where !a.Attribute("AssemblyQualifiedName").Value.Contains("Microsoft.Crm")
    			  select new {
    			  CustomStepName = a.Attribute("DisplayName").Value,
    			  AssemblyName = a.Attribute("AssemblyQualifiedName").Value,
    			  HasArguments = a.Descendants($"{mxswa}ActivityReference.Arguments").Any()}
  4. You’ll get something like this. Note that the workflow in my case is “Blank Workflow with Custom Step”. Change this to the one you are trying to profile.Query

Issue 1: Cannot see the workflow step.

Blank Step

If you cannot see the workflow step, that means that there is a mismatch between the Microsoft.Xrm.Sdk.Workflow.dll version and/or your custom workflow step version. Go the folder with the Plugin Registration tool and check the version of the Microsoft.Xrm.Sdk.Workflow.dll version. The important thing is if the major version or minor version of the Microsoft.Xrm.Sdk.Workflow.dll assembly in Plugin Registration tool folder, is different from the one in the Workflow XAML, you will not see the step displayed.

CRM 2016 Plugin Reg Tool
CRM 2016 Plugin Registration Tool
CRM 2016 Update 1 Plugin Reg Tool.png
CRM 2016 Update 1 Plugin Registration Tool

Compare this with the results of the LINQ query and confirm that the major version and minor version match. Do this same for your custom workflow assembly as well.

Issue 2: NullReferenceException

There is a bug in the Plugin Registration Tool, that doesn’t allow you to profile workflow steps, that doesn’t have any argument. If you try to do this, you will get this exception.

Null Reference.png

The workaround for this is to add a dummy argument, to keep the plugin profiler happy.


Hope this helps with your debugging efforts.

Gotcha: Optional Action Parameters

Yesterday, when I was looking into some failed tests, I discovered a quirk of action. If you invoke an action and don’t pass in the arguments that are optional, it doesn’t mean this is going to come up as null in the execution context.

Below is the action I used to replicate the behaviour.


When I execute the action and don’t set any arguments, here is what comes through in the execution context.

Argument Type Value
 Boolean  false
 DateTime  0001-01-01T00:00:00
 Decimal  0
 Entity  null
 EntityReference  null
 Float  0
 Int  0
 Money  null
 Picklist  null
 String  null
 EntityCollection  null

The interesting things to note are:

  1. Action arguments, don’t behave the same as AttributeCollection. When you retrieve an entity using the SDK, get the attribute just using the key (not GetAttributeValue), the key won’t exist in the collection, if the attribute value is null. This is not the same behaviour with the action arguments. The key will always be there in the context, even if was not set during the action invocation.
  2. The default value of action arguments = default value of the primitive type

This essentially means that if you are going to do some processing based on the action argument that is a primitive type (Boolean, DateTime, Decimal, Float, Int, String), don’t make it optional. For e.g. if you make a boolean argument optional, how would you differentiate between an action invocation with the argument set to false, and another one which did not set the argument at all?

tl;dr; For action arguments (both input and output) of type Boolean, DateTime, Decimal, Float, Int and String avoid optional.

Gotcha: Plugin Running Async

Generally when you want run a set of tasks that are not realtime, you would do this via Workflow. On the contrary when you want something to happen straightaway, you would do this using a Plugin. Before CRM2015, there was only one type of workflow. Starting with CRM2015 a workflow can be realtime or async. A plugin also can be run asynchronously or synchronously. To summarise quickly:

  1. A realtime workflow is similar to a plugin
  2. An asynchronous plugin is similar to a workflow

Take this particular scenario: You have a plugin that runs synchronously on the Update of a certain entity. Later down the track, you decide you want this plugin to run async, as the set of tasks performed by the plugin doesn’t really have to be realtime and you want to improve the performance of the core operation.

In this scenario, you have to be mindful of one particular plugin behaviour: transaction. When an exception is thrown inside a plugin, the core operation won’t succeed as the exception in the plugin will cause the transaction to rollback. When you change the Execution Mode to “Asynchronous”, you will still see that the plugin has failed in System Jobs area, but the transaction won’t be rolled back.

For eg. take this simple code:

using Microsoft.Xrm.Sdk;
using System;

namespace CrmExperimentPlugin
    public class CreateContactPlugin : IPlugin
        #region Secure/Unsecure Configuration Setup
        private string _secureConfig = null;
        private string _unsecureConfig = null;

        public CreateContactPlugin(string unsecureConfig, string secureConfig)
            _secureConfig = secureConfig;
            _unsecureConfig = unsecureConfig;
        public void Execute(IServiceProvider serviceProvider)
            ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = factory.CreateOrganizationService(context.UserId);
            Entity entity = (Entity)context.InputParameters["Target"];

            Entity contact = new Entity("contact");
            contact["firstname"] = "Max";
            contact["lastname"] = "Power";

            throw new InvalidPluginExecutionException("Exception raised after contact create");

Assuming that the plugin is registered post-create on a particular entity and Execution Mode is “Synchronous”, there will be no contact called “Max Power” at the end of the plugin execution. But if the same plugin is registered as post-create and asynchronous, there will be a contact called “Max Power” as the exception doesn’t rollback the transaction.

If you reference Event Execution Pipeline in msdn, it says this about the plugin transaction:

Stages 20 and 40 are guaranteed to be part of the database transaction while stage 10 may be part of the transaction.

But this is not entirely true, as it also depends on the Execution Mode.

tl;dr; Register plugins to run sychronously, if you need the transaction to rollback on plugin exception and need to validate certain criteria for the core operation to succeed. If you don’t need these features, use a workflow.

DateTime AddMonths

I recently encountered a weird workflow behaviour that was caused due to my misunderstanding of how DateTime.AddMonths works. The issue happened with a recurring workflow. Since there is no recurring workflow functionality OOB in CRM there are couple of approaches to do this.

  1. Scheduling recurring Dynamics CRM workflows with FetchXML
  2. Asynchronous Batch Process Solution

I have used a slighly modified version of these approaches to do the recurring workflow functionality. There is only one instance of workflow running every day, and it basically uses fetchxml to grab records that meet a certain criteria and sends out notifications. One of my criteria is “start date 6 months before today” used for 6 month notification. But when you use DateTime.Now.AddMonths(-6) to populate the start date condition, I encountered this behavior.

Here is a quick summary of the issue

Current Date Code Expected Actual
28/08/2015 DateTime.Today.AddMonths(-6).ToString(“s”) 28/02/2015 28/02/2015
29/08/2015 DateTime.Today.AddMonths(-6).ToString(“s”) 01/03/2015 28/02/2015
30/08/2015 DateTime.Today.AddMonths(-6).ToString(“s”) 02/03/2015 28/02/2015
31/08/2015 DateTime.Today.AddMonths(-6).ToString(“s”) 03/03/2015 28/02/2015

So, when the workflow is running on these 4 days from 28/08/2015 to 31/08/2015 and calculating 6 months before the current date, it will produce the same date. As a result when the calculated date is used in a fetch condition as a filter, it will pick the same record(s) on these 4 dates.

The solution to fix your definition of month to 30 days, if that is acceptable. In my case it was OK, as all I did was send out notifications.

tldr; If the resulting day is not a valid day in the resulting month, the last valid day of the resulting month is used


Gotcha: fn_UTCToLocalTime and fn_LocalTimeToUTC

There are not many scenarios where you would directly operate against an entity base table in CRM. There two scenarios that I can think of are:

  1. Reporting
  2. Data Migration/Integration

Filtered view is the recommended approach when it comes to writing reports for OnPremise CRM server. But if the performance is not really upto the mark, you are forced to either optimise the query by creating indexes/statistics against the correct tables, or directly operate against the base tables.

CRM 2015 Update 1 has introduced the option to create a datetime field as Timezone independent, but in all versions prior to this one, you’ll have to take care of the UTC/Local and Local/UTC conversion, when it comes to datetime fields. CRM does this using fn_UTCToLocalTime and fn_LocalTimeToUTC UDFs that are defined in the MSCRM database.

When you are connected into the SQL database as an Active Directory user, who is not a user in CRM, both these functions will return null. The reason CRM needs to know who the user is to find out the Timezone preferences that are stored again the user’s profile. This is needed to perform the UTC/Local conversion.

In order to understand why this happens, you’ll have to understand how CRM finds out who the current user is.

CRM first tries to match the Active Directory login name of the user against the DomainName column of SystemUser table in MSCRM database. If it can’t find a match, it then tries to match the SystemUserId in the SystemUser table with the context_info on the connection.

Query to understand the behaviour

--user has access to mscrm db, but not a user in CRM
EXECUTE AS user ='CONTOSO\Max.Power'
select dbo.fn_UTCToLocalTime(CreatedOn) from

DECLARE @systemuserid varbinary(128);
select @systemuserid=SystemUserId FROM dbo.systemuser where DomainName='CONTOSO\Alan.Smith'

--impersonate the context of an user who in CRM
SET context_info @systemuserid
select dbo.fn_UTCToLocalTime(CreatedOn) from

--reset context

When you run this query in SSMS, you’ll get something similar to this.

Query Result
Query Result

As you can see the first fn_UTCToLocalTime returned null, but the second one doesn’t, as a different user is impersonated using the context_info. Please be mindful of this behaviour when you are directly working against the base tables.

Opportunity Product Permissions

When an assumption is made that something very basic, can be easily configured using OOB functionalities, that is when I find myself stuck occasionally and having to rethink on how to implement a feature. I recently had one such experience. The requirement was to allow users in certain security role, to delete opportunity products, but not opportunities.My first thought was to use security role, to modify the permissions for opportunity products. To my surprise, I was unable to find opportunity products in Security Role.

It turns out Opportunity Product, Invoice Product, Quote Product and Order Product share a unique trait: they don’t have separate permissions and use the permission of their parent. Such being the limitation, I could implement this using ribbons or plugins. I implemented this using ribbon. Here are the steps

  1. Grant Delete permission on the Opportunity entity for the appropriate security role
  2.  Use Ribbon Workbench to edit the ribbon for opportunity entity
  3. Add a new Enable Rule of type Custom Javascript Rule. I am calling a function in the Javascript webresource
  4.  Add the Enable Rule to the following commands:  
    • Mscrm.DeletePrimaryRecord
    • Mscrm.DeleteSelectedRecord
    • Mscrm.HomePageGrid.DeleteSplitButtonCommand

This change should be done on HomePage, Form and Subgrid ribbons. The code for the getOppDeletePermissionByRole itself is quite simple, as I am using XrmServiceToolkit.

var CVN = window.CVN || {};

CVN.getOppDeletePermissionByRole = function() {
 return !XrmServiceToolkit.Soap.IsCurrentUserRole('1.1 CRM - Base User Role');

window.CVN = CVN;

I did this on a CRM2011 organisation, but the process in same for a CRM2015 organisation. You’ll just be editing the command bar instead of the ribbon. Here are the relevant buttons in CRM2015, whose command you’ll need to edit.

Create New User Error

Yesterday, I got the following error when I tried to add a new user:

You are attempting to create a user with a domain logon that is already used by another user. Select another domain logon and try again.

The strange thing I noticed about the error is that, the user I was trying to add was not already there in CRM. I checked this in the SystemUser table and confirmed that it is definitely not there. After tracing SQL Server, I found CRM uses SystemUserAuthentication table to do this check.

Here is the sequence to events

No user A -> Backup organisation database -> User A created -> Restore organisation db from backup

In my situation, the error started happening after I restored the organisation db from the backup. At the time of the backup, the user I was trying to add had not been created. After the organisation db was restored from backup, the SystemUser table in the organisation database does not contain User A, but the MSCRM_CONFIG does (as per SystemUserAuthentication). This is the root cause of this error message.

The fix was to delete the organisation and re-import the organisation from the Deployment Manager. Once this is done, the new user can be added without any issue. This was the additional step I had to perform after I restored the db from the backup.