In last week’s post, we took a look at how a custom Workflow activity can be implemented within Dynamics CRM/Dynamics 365 for Customer Engagement to obtain the name of the user who triggered the workflow. It may be useful to retrieve this information for a variety of different reasons, such as debugging, logging user activity or to automate the population of key record information. I mentioned in the post the “treasure trove” of information that the IWorkflowContext interface exposes to developers. Custom Workflow activities are not unique in having execution-specific information exposable, with an equivalent interface at our disposal when working with plug-ins. No prizes for guessing its name – the IPluginExecutionContext.

When comparing both interfaces, some comfort can be found in that they share almost identical properties, thereby allowing us to replicate the functionality demonstrated in last weeks post as Post-Execution Create step for the Lead entity. The order of work for this is virtually the same:

  1. Develop a plug-in C# class file that retrieves the User ID of the account that has triggered the plugin.
  2. Add supplementary logic to the above class file to retrieve the Display Name of the User.
  3. Deploy the compiled .dll file into the application via the Plug-in Registration Tool, adding on the appropriate execution step.

The emphasis on this approach, as will be demonstrated, is much more focused towards working outside of the application; something you may not necessarily be comfortable with. Nevertheless, I hope that the remaining sections will provide enough detail to enable you to replicate within your own environment.

Developing the Class File

As before, you’ll need to have ready access to a Visual Studio C# Class file project and the Dynamics 365 SDK. You’ll also need to ensure that your project has a Reference added to the Microsoft.Xrm.Sdk.dll. Create a new Class file and copy and paste the following code into the window:

using System;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace D365.BlogDemoAssets.Plugins
{
    public class PostLeadCreate_GetInitiatingUserExample : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            // Obtain the execution context from the service provider.

            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            // Obtain the organization service reference.
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            // The InputParameters collection contains all the data passed in the message request.
            if (context.InputParameters.Contains("Target") &&
                context.InputParameters["Target"] is Entity)

            {
                Entity lead = (Entity)context.InputParameters["Target"];

                //Use the Context to obtain the Guid of the user who triggered the plugin - this is the only piece of information exposed.
      
                Guid user = context.InitiatingUserId;

                //Then, use GetUserDisplayCustom method to retrieve the fullname attribute value for the record.

                string displayName = GetUserDisplayName(user, service);

                //Build out the note record with the required field values: Title, Regarding and Description field

                Entity note = new Entity("annotation");
                note["subject"] = "Test Note";
                note["objectid"] = new EntityReference("lead", lead.Id);
                note["notetext"] = @"This is a test note populated with the name of the user who triggered the Post Create plugin on the Lead entity:" + Environment.NewLine + Environment.NewLine + "Executing User: " + displayName;

                //Finally, create the record using the IOrganizationService reference

                service.Create(note);
            }
        }
    }
}

Note also that you will need to rename the namespace value to match against the name of your project.

To explain, the code replicates the same functionality developed as part of the Workflow on last week’s post – namely, create a Note related to a newly created Lead record and populate it with the Display Name of the User who has triggered the plugin.

Retrieving the User’s Display Name

After copying the above code snippet into your project, you may notice a squiggly red line on the following method call:

The GetUserDisplayName is a custom method that needs to be added in manually and is the only way in which we can retrieve the Display Name of the user, which is not returned as part of the IPluginExecutionContext. We, therefore, need to query the User (systemuser) entity to return the Full Name (fullname) field, which we can then use to populate our newly create Note record. We use a custom method to return this value, which is provided below and should be placed after the last 2 curly braces after the Execute method, but before the final 2 closing braces:

private string GetUserDisplayName(Guid userID, IOrganizationService service)
    {
        Entity user = service.Retrieve("systemuser", userID, new ColumnSet("fullname"));
        return user.GetAttributeValue<string>("fullname");
    }

Deploy to the application using the Plug-in Registration Tool

The steps involved in this do not differ greatly from what was demonstrated in last week’s post, so I won’t repeat myself 🙂 The only thing you need to make sure you do after you have registered the plug-in is to configure the plug-in Step. Without this, your plug-in will not execute. Right-click your newly deployed plug-in on the main window of the Registration Tool and select Register New Step:

On the form that appears, populate the fields/values indicated below:

  • Message: Create
  • Primary Entity: Lead
  • Run in User’s Context: Calling User
  • Event Pipeline Stage of Execution: Post-Operation

The window should look similar to the below if populated correctly. If so, then you can click Register New Step to update the application:

All that remains is to perform a quick test within the application by creating a new Lead record. After saving, we can then verify that the plug-in has created the Note record as intended:

Having compared both solutions to achieve the same purpose, is there a recommended approach to take?

The examples shown in the past two blog posts indicate excellently how solutions to specific scenarios within the application can be achieved via differing ways. As clearly evidenced, one could argue that there is a code-heavy (plug-in) and a light-touch coding (custom Workflow assembly) option available, depending on how comfortable you are with working with the SDK. Plug-ins are a natural choice if you are confident working solely within Visual Studio or have a requirement to perform additional business logic as part of your requirements. This could range from complex record retrieval operations within the application or even an external integration piece involving specific and highly tailored code. The Workflow path clearly favours those of us who prefer to work within the application in a supported manner and, in this particular example, can make certain tasks easier to accomplish. As we have seen, the act of retrieving the Display Name of a user is greatly simplified when we go down the Workflow route. Custom Workflow assemblies also offer greater portability and reusability, meaning that you can tailor logic that can be applied to multiple different scenarios in the future. Code reusability is one of the key drivers in many organisations these days, and the use of custom Workflow assemblies neatly fits into this ethos.

These are perhaps a few considerations that you should make when choosing the option that fits the needs of your particular requirement, but it could be that the way you feel most comfortable with ultimately wins the day – so long as this does not compromise the organisation as a consequence, then this is an acceptable stance to take. Hopefully, this short series of posts have demonstrated the versatility of the application and the ability to approach challenges with equally acceptable pathways for resolution.

It’s sometimes useful to determine the name of the user account that executes a Workflow within Dynamics CRM/Dynamics 365 for Customer Engagement (CRM/D365CE). What can make this a somewhat fiendish task to accomplish is the default behaviour within the application, which exposes very little contextual information each time a Workflow is triggered. Take, for example, the following simplistic Workflow which creates an associated Note record whenever a new Lead record is created:

The Note record is set to be populated with the default values available to us regarding the Workflow execution session – Activity Count, Activity Count including Process and Execution Time:

We can verify that this Workflow works – and view the exact values of these details – by creating a new Lead record and refreshing the record page:

The Execution Time field is somewhat useful, but the Activity Count Activity Count including Process values relate to Workflow execution sessions and are only arguably useful for diagnostic review – not something that end users of the application will generally be interested in 🙂

Going back to the opening sentence of this post, if we were wanting to develop this example further to include the Name of the user who executed the Workflow in the note, we would have to look at deploying a Custom Workflow Assembly to extract the information out. The IWorkflowContext Interface is a veritable treasure trove of information that can be exposed to developers to retrieve not just the name of the user who triggers a Workflow, but the time when the corresponding system job was created, the Business Unit it is being executed within and information to determine whether the Workflow was triggered by a parent. There are three steps involved in deploying out custom code into the application for utilisation in this manner:

  1. Develop a CodeActivity C# class file that performs the desired functionality.
  2. Deploy the compiled .dll file into the application via the Plugin Registration Tool.
  3. Modify the existing Workflow to include a step that accesses the custom Workflow Activity.

All of these steps will require ready access to Visual Studio, a C# class plugin project (either a new one or existing) and the CRM SDK that corresponds to your version for the application.

Developing the Class File

To begin with, make sure your project includes References to the following Frameworks:

  • System.Activities
  • Microsoft.Xrm.Sdk
  • Microsoft.Xrm.Sdk.Workflow

Add a new Class (.cs) file to your project and copy & paste the below code, overwriting any existing code in the window. Be sure to update the namespace value to reflect your project name:

using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;

namespace D365.Demo.Plugins
{
    public class GetWorkflowInitiatingUser : CodeActivity
    {
        protected override void Execute(CodeActivityContext executionContext)
        {
            IWorkflowContext workflowContext = executionContext.GetExtension<IWorkflowContext>();
            CurrentUser.Set(executionContext, new EntityReference("systemuser", workflowContext.InitiatingUserId));
        }

        [Output("Current User")]
        [ReferenceTarget("systemuser")]
        public OutArgument<EntityReference> CurrentUser { get; set; }
    }
}

Right-click your project and select Build. Verify that no errors are generated and, if so, then that’s the first step done and dusted 🙂

Deploy to CRM/D365CE

Open up the Plugin Registration Tool and connect to your desired instance. If you are deploying an existing, updated plugin class, then right-click it on the list of Registered Plugins & Custom Workflow Activities and click Update; otherwise, select Register -> Register New Assembly. The same window opens in any event. Load the newly built assembly from your project (can be located in the \bin\Debug\ folder by default) and ensure the Workflow Activity entry is ticked before selecting Register Selected Plugins:

After registration, the Workflow Activity becomes available for use within the application; so time to return to the Workflow we created earlier!

Adding the Custom Workflow Activity to a Process

By deactivating the Workflow Default Process Values Example Workflow and selecting Add Step, we can verify that the Custom Workflow Assembly is available for use:

Select the above, making sure first of all that the option Insert Before Step is toggled (to ensure it appears before the already configured Create Note for Lead step). It should look similar to the below if done correctly:

Now, when we go and edit the Create Note for Lead step, we will see a new option under Local Values which, when selected, bring up a whole range of different fields that correspond to fields from the User Entity. Modify the text within the Note to retrieve the Full Name value and save it onto the Note record, as indicated below:

After saving and reactivating the Workflow, we can verify its working by again creating a new Lead record and refreshing to review the Note text:

All working as expected!

The example shown in this post has very limited usefulness in a practical business scenario, but could be useful in different circumstances:

  • If your Workflow contains branching logic, then you can test to see if a Workflow has executed by a specific user and then perform bespoke logic based on this value.
  • Records can be assigned to other users/teams, based on who has triggered the Workflow.
  • User activity could be recorded in a separate entity for benchmarking/monitoring purposes.

It’s useful to know as well that the same kind of functionality can also be deployed when working with plugins as well in the application. We will take a look at how this works as part of next week’s blog post.

Dynamics CRM/Dynamics 365 for Customer Engagement (CRM/D365CE) is an incredibly flexible application for the most part. Regardless of how your business operates, you can generally tailor the system to suit your requirements and extend it to your heart’s content; often to the point where it is completely unrecognisable from the base application. Notwithstanding this argument, you will come across aspects of the application that are (literally) hard-coded to behave a certain way and cannot be straightforwardly overridden via the application interface. The most recognisable example of this is the Lead Qualification process. You are heavily restricted in how this piece of functionality acts by default but, thankfully, there are ways in which it can be modified if you are comfortable working with C#, JScript and Ribbon development.

Before we can start to look at options for tailoring the Lead Qualification process, it is important to understand what occurs during the default action within the application. In developer-speak, this is generally referred to as the QualifyLead message and most typically executes when you click the button below on the Lead form:

When called by default, the following occurs:

  • The Status/Status Reason of the Lead is changed to Qualified, making the record inactive and read-only.
  • A new OpportunityContact and Account record is created and populated with (some) of the details entered on the Lead record. For example, the Contact record will have a First Name/Last Name value supplied on the preceding Lead record.
  • You are automatically redirected to the newly created Opportunity record.

This is all well and good if you are able to map your existing business processes to the application, but most organisations will typically differ from the applications B2B orientated focus. For example, if you are working within a B2C business process, creating an Account record may not make sense, given that this is typically used to represent a company/organisation. Or, conversely, you may want to jump straight from a Lead to a Quote record. Both of these scenarios would require bespoke development to accommodate currently within CRM/D365CE. This can be broadly categorised into two distinct pieces of work:

  1. Modify the QualifyLead message during its execution to force the desired record creation behaviour.
  2. Implement client-side logic to ensure that the user is redirected to the appropriate record after qualification.

The remaining sections of this post will demonstrate how you can go about achieving the above requirements in two different ways.

Our first step is to “intercept” the QualifyLead message at runtime and inject our own custom business logic instead

I have seen a few ways that this can be done. One way, demonstrated here by the always helpful Jason Lattimer, involves creating a custom JScript function and a button on the form to execute your desired logic. As part of this code, you can then specify your record creation preferences. A nice and effective solution, but one in its guise above will soon obsolete as a result of the SOAP endpoint deprecation. An alternative way is to instead deploy a simplistic C# plugin class that ensures your custom logic is obeyed across the application, and not just when you are working from within the Lead form (e.g. you could have a custom application that qualifies leads using the SDK). Heres how the code would look in practice:

public void Execute(IServiceProvider serviceProvider)
    {
        //Obtain the execution context from the service provider.

        IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

        if (context.MessageName != "QualifyLead")
            return;

        //Get a reference to the Organization service.

        IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
        IOrganizationService service = factory.CreateOrganizationService(context.UserId);

        //Extract the tracing service for use in debugging sandboxed plug-ins

        ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

        tracingService.Trace("Input parameters before:");
        foreach (var item in context.InputParameters)
        {
            tracingService.Trace("{0}: {1}", item.Key, item.Value);
        }

        //Modify the below input parameters to suit your requirements.
        //In this example, only a Contact record will be created
        
        context.InputParameters["CreateContact"] = true;
        context.InputParameters["CreateAccount"] = false;
        context.InputParameters["CreateOpportunity"] = false;

        tracingService.Trace("Input parameters after:");
        foreach (var item in context.InputParameters)
        {
            tracingService.Trace("{0}: {1}", item.Key, item.Value);
        }
    }

To work correctly, you will need to ensure this is deployed out on the Pre-Operation stage, as by the time the message reaches the Post-Operation stage, you will be too late to modify the QualifyLead message.

The next challenge is to handle the redirect to your record of choice after Lead qualification

Jason’s code above handles this effectively, with a redirect after the QualifyLead request has completed successfully to the newly created Account (which can be tweaked to redirect to the Contact instead). The downside of the plugin approach is that this functionality is not supported. So, if you choose to disable the creation of an Opportunity record and then press the Qualify Lead button…nothing will happen. The record will qualify successfully (which you can confirm by refreshing the form) but you will then have to manually navigate to the record(s) that have been created.

The only way around this with the plugin approach is to look at implementing a similar solution to the above – a Web API request to retrieve your newly created Contact/Account record and then perform the necessary redirect to your chosen entity form:

function redirectOnQualify() {

    setTimeout(function(){
        
        var leadID = Xrm.Page.data.entity.getId();

        leadID = leadID.replace("{", "");
        leadID = leadID.replace("}", "");

        var req = new XMLHttpRequest();
        req.open("GET", Xrm.Page.context.getClientUrl() + "/api/data/v8.0/leads(" + leadID + ")?$select=_parentaccountid_value,_parentcontactid_value", true);
        req.setRequestHeader("OData-MaxVersion", "4.0");
        req.setRequestHeader("OData-Version", "4.0");
        req.setRequestHeader("Accept", "application/json");
        req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
        req.setRequestHeader("Prefer", "odata.include-annotations=\"OData.Community.Display.V1.FormattedValue\"");
        req.onreadystatechange = function () {
            if (this.readyState === 4) {
                req.onreadystatechange = null;
                if (this.status === 200) {
                    var result = JSON.parse(this.response);
                    
                    //Uncomment based on which record you which to redirect to.
                    //Currently, this will redirect to the newly created Account record
                    var accountID = result["_parentaccountid_value"];
                    Xrm.Utility.openEntityForm('account', accountID);

                    //var contactID = result["_parentcontactid_value"];
                    //Xrm.Utility.openEntityForm('contact', contactID);

                }
                else {
                    alert(this.statusText);
                }
            }
        };
        req.send();
        
    }, 6000);     
}

The code is set to execute the Web API call 6 seconds after the function triggers. This is to ensure adequate time for the QualifyLead request to finish and make the fields we need available for accessing.

To deploy out, we use the eternally useful Ribbon Workbench to access the existing Qualify Lead button and add on a custom command that will fire alongside the default one:

As this post has hopefully demonstrated, overcoming challenges within CRM/D365CE can often result in different – but no less preferred – approaches to achieve your desired outcome. Let me know in the comments below if you have found any other ways of modifying the default Lead Qualification process within the application.

The sheer breadth of ways that you can utilise Dynamics CRM/Dynamics 365 for Enterprise (CRM/D365E) can sometimes boggle the mind. Whether it’s through a traditional web browser, mobile app, the new interactive service hub or even through your own website created via the SDK, organisations have an ever-increasing array of routes they can go down when deploying the application into their environment. Despite this, more often than not, you would expect a “standard” deployment to involve using the application via a web browser, either on a local machine or potentially via a Remote Desktop Session (RDS) instance. Whilst Microsoft’s support articles provide fairly definitive software requirements when working on a Windows desktop machine, it is difficult to determine if, for example, Google Chrome on a Windows Server 2012 RDS session is supported. This is an important omission that requires clarification and is worth discussing further to determine if a definitive conclusion can be reached, based on the available evidence.

In this week’s post, I will attempt to sleuth through the various pieces of evidence I can find on this subject, sprinkling this with some experience that I have had with CRM/D365E and RDS, to see if any definitive conclusion can be established.

Before we get into the heart of the matter, it may be useful to provide a brief overview of what RDS is

RDS is a fancy way of describing connecting to a remote computer via the Remote Desktop Connection client on your Windows or OS of choice. Often referred to as Terminal Services, it is a de facto requirement when accessing remote servers for a variety of reasons. Most commonly, you will witness it deployed as part of an internal corporate network, as a mechanism for users to “remote on” when working outside the office. Due to the familiarity of Windows Server compared with each versions corresponding Desktop OS, the look and feel of working on a normal computer can be achieved with minimal effort, and you can often guarantee that the same types of programmes will also work without issue.

Whilst RDS is still frequently used, it could be argued to have taken a back seat in recent years with the rise in virtualisation technologies, from the likes of Citrix and VMware. These solutions tend to offer the same benefits an RDS server can, but places more emphasis on utilising a local desktop environment to essentially stream desktops/applications to end users. As a result of the rise of these technologies, RDS is perhaps entering a period of uncertainty; whilst it will continue to be essential for remote server management, there are arguably much better technologies available that provide an enriched end-user experience, but offer the same benefits of having a centralised server within a backed up/cloud environment.

Now that you (hopefully!) have a good overview of what RDS is, let’s take a look at the evidence available in relation to CRM/D365E and RDS

Evidence #1: Technet Articles

The following TechNet articles provide, when collated together, a consolidated view of supported internet browsers and operating systems for CRM/D365:

From this, we can distill the following:

  • Windows 10, 8.1, 8 and 7 are supported, so long as they are using a “supported” browser:
    • Internet Explorer 10 is supported for Windows 7 and 8 only.
    • Internet Explorer 11 is supported for all Windows OS’s, with the exception of 8.
    • Edge is supported for Windows 10 only.
    • Firefox and Chrome are supported on all OS’s, so long as they are running the latest version.
  • OS X 10.8 (Mountain Lion), 10.9 (Mavericks) and 10.10 Yosemite are supported for Safari only, running the latest version
  • Android 10 is supported for the latest version of Chrome only
  • iPad is supported for the latest version of Safari only (i.e. the latest version of iOS)

The implication from this should be clear – although the following Windows Server devices (that are currently in mainstream support) can be running a supported web browser, they are not covered as part of the above operating server list:

  • Windows Server 2016
  • Windows Server 2012 R2
  • Windows Server 2012

Evidence #2: Notes from the Field

I have had extensive experience both deploying into and supporting CRM/D365E environments running RDS. These would typically involve servers with significant user load (20-30 per RDS server) and, the general experience and feedback from end users has always been…underwhelming. All issues generally came down to the speed of the application which, when compared to running on a standard, local machine, was at a snail’s pace by comparison. Things like loading a form, an entity view or Dialog became tortuous affairs and led to serious issues with user adoption across the deployments. I can only assume that the amount of local CPU/Memory required for CRM/D365E when running inside a web application was too much for the RDS server to handle; this was confirmed by frequent CPU spikes and high memory utilisation on the server.

I can also attest to working with Microsoft partners who have explicitly avoided having issues concerning RDS and CRM/D365E in-scope as part of any support agreement. When this was queried, the reasoning boiled down to the perceived hassle and complexity involved in managing these types of deployment.

To summarise, I would argue that this factors in additional ammunition for Evidence piece #1, insomuch as that RDS compatible servers are not covered on the supported operating system lists because these issues are known about generally.

Evidence #3: What Microsoft Actually Say

I was recently involved as part of a support case with Microsoft, where we were attempting to diagnose some of the performance issues discussed above within an RDS environment. The support professional assigned to the case came back and stated the following in regards to RDS and CRM/D365E:

…using Windows remote desktop service is supported but unfortunately using Windows server 2012 R2 is not supported. You have to use Windows server 2012. Also windows server 2016 is out of our support boundaries.

Whilst this statement is not backed up by an explicit online source (and I worry whether some confusion has been derived from the Dynamics 365 for Outlook application – see below for more info on this), it can be taken as saying that Windows Server 2012 is the only supported operating system that can be used to access CRM/D365E, with one of the supported web browsers mentioned above.

The Anomalous Piece of Evidence: Dynamics 365 for Outlook Application

Whilst it may not be 100% clear cut in regards to supported server operating systems, we can point to a very definitive statement in respect to the Dynamics 365 for Outlook application when used in conjunction with RDS:

Dynamics 365 for Outlook is supported for running on Windows Server 2012 Remote Desktop Services

Source: https://technet.microsoft.com/en-us/library/hh699743.aspx

Making assumptions here again, but can we take this to mean that the web application is supported within Windows Server 2012 RDS environments, as suggested by the Microsoft engineer above? If not, then you may start thinking to yourself “Well, why not just use this instead of a web browser on RDS to access CRM/D365E?”. Here are a few reasons why you wouldn’t really want to look at rolling out the Dynamics 365 for Outlook application any time soon within RDS:

  • If deploying the application into offline mode, then you will be required to install a SQL Express instance onto the machine in question. This is because the application needs to store copies of your synchronised entity data for whenever you go offline. The impact of this on a standard user machine will be minimal at best, but on a shared desktop environment, could lead to eventual performance issues on the RDS server in question
  • With the introduction of new ways to work within CRM/D365 data in an efficient way, such as with the Dynamics 365 App for Outlook, the traditional Outlook client is something that is becoming less of a requirement these days. There are plenty of rumours/commentary on the grapevine that the application may be due for depreciation in the near future, and even Microsoft have the following to say on the subject:

    Dynamics 365 App for Outlook isn’t the same thing as Dynamics 365 for Outlook. As of the December 2016 update for Dynamics 365 (online and on-premises), Microsoft Dynamics 365 App for Outlook paired with server-side synchronization is the preferred way to integrate Microsoft Dynamics 365 with Outlook.

  • I have observed performance issues with the add-in myself in the past – outlook freezing, the occasional crash and also issues with the Outlook ribbon displaying incorrectly.

As you can probably tell, I am not a big fan of the add-in, but the writing on the wall is fairly clear – Microsoft fully supports you accessing CRM/D365E from the Outlook client on Windows Server 2012 RDS.

After reviewing all the evidence, do we have enough to solve this case?

Whilst there is a lot of evidence to consider, the main thing I would highlight is the lack of a “smoking gun” in what has been reviewed. What I mean by this is the lack of a clear support article that states either “X Browser is supported on Windows Server X” or “X Browser is NOT supported on Windows Server X“. Without any of these specific statements, we are left in a situation where we have to infer that RDS is not a supported option for using the CRM/D365E web application. Certainly, the experience I have had with the web client in these environment types would seem to back this up, which may go some way towards explaining the reason why this is not implicitly supported.

So where does this leave you if you are planning to deploy CRM/D365E within an RDS environment? Your only option is to ensure that your RDS environment is running Windows Server 2012 and that your users are utilising the Outlook client, given that there is a very clear statement regarding its supportability. If you are hell bent on ensuring that your end users have the very best experience with CRM/D365E, then I would urge you to reconsider how your environment is configured and, if possible, move to a supported configuration – whether that’s local desktop or a VDI, running your browser of choice. Hopefully, the benefits of utilising the application will far outweigh any overriding concerns and business reasons for using RDS in the first place.

One of the nice things about working with lookup fields on entity forms is the ability to filter the results programmatically via a form level JScript function. The steps for doing this, thankfully, are rather straightforward:

  1. Prepare a FetchXML filter snippet that applies the type of filtering you want to perform. This can either be written manually, or you can build your filter within Advanced Find, download the FetchXML and then copy + paste the <filter>…</filter> node.
  2. Access the control using the Xrm.Page.getControl object, and then use the addCustomFilter method to apply the filter
  3. Finally, setup your function to fire on the OnLoad event, using the addPreSearch method to actually apply the filter to the lookup control.

If you are still scratching your head at the above, then fortunately there is an MSDN article on the subject which provides further guidance, including a handy example snippet that you can modify and apply straight to your CRM instance. What I like most about this method is that its application is simple, but its potential is quite huge. Utilising conditional logic within your Jscript function means that you could potentially filter results based on values on the form, via a web service request to another CRM record or based on the value in an external CRM/ERP system. Used correctly and prudently, it can help to make form level data inputting much more easier and context sensitive.

CRM has a number of special field types that are reserved for certain system entities. One example is the Customer Data Type field. This is, in essence, a special kind of lookup control that spans two entities – Account & Contact. System customisers are able to create additional fields with this data type, but are unable to create a customer lookup control that spans across 2 entities of choice. This is a real shame, as I think this feature would be really welcome and be widely applicable to a number of different business scenarios.

I was recently working with this particular field on the Contact entity in order to meet a specific requirement as part of a solution – for only certain Account records to appear and for no Contact records to be made available to select. One potential work-around would be to just create a new 1:N relationship between Account:Contact, but this seems rather unnecessary when there is already a relationship in place that could be modified slightly in order to suit requirements. I was therefore interested in finding out whether the addCustomFilter/addPresearch methods could be utilised in this case. After a small, yet sustained, period of severe head-banging, I was able to get this working; although the solution could be argued as being less than ideal.

For the above requirement, lets assume we have a custom field on our Account entity – Company Type – which we are using to indicate what type of UK company an Account is:

4

Once created, we then populate our CRM sample Account records with the value of ‘Limited Company (Ltd.) and run a quick Advanced Find to confirm they return OK:

7

8

Now, for this example, we want to apply a very basic filter on the Customer field on our Contact entity to only show Account records that equal ‘Limited Company (Ltd.)’. Here is our “first attempt” JScript function(s) to achieve this:

function onLoad() {

    Xrm.Page.getControl("parentcustomerid").addPreSearch(modifyCustomerLookupField);

}


function modifyCustomerLookupField() {

	fetchXML = "<filter type='and'><condition attribute='jg_companytype' operator='eq' value='140250000' /></filter>";
	Xrm.Page.getControl("parentcustomerid").addCustomFilter(fetchXML);


}

After creating a JScript Library, adding it to a form, calling the onLoad() function as part of the OnLoad event and, finally (whew!), pressing the magnifying class on the control, we immediately get an error message:

1

Here’s the error message:

5

As a next step, to try and resolve this, I remembered within the FetchXml <filter>…</filter> node, you can specify the name of the entity that you want to filter on. This is typically required when you are using <link-entity> in order to join together multiple records. So I modified my JScript function to include the entity name within the fetchXML filter:

function onLoad() {

    Xrm.Page.getControl("parentcustomerid").addPreSearch(modifyCustomerLookupField);

}


function modifyCustomerLookupField() {

	fetchXML = "<filter type='and'><condition entityname='account' attribute='jg_companytype' operator='eq' value='140250000' /></filter>";
	Xrm.Page.getControl("parentcustomerid").addCustomFilter(fetchXML);


}

With fingers crossed, I then tried again…but still got an error message, but a slightly different one. Some progress at least!

2

3

Once my head had returned from the desk in front of me, I looked back at the original error message, in particular the fact that the Contact entity does not contain the new field we have on our Account. This is to be expected, but is there some way we can “fool” CRM by also having a field with the same name on Contact, but which is not used for anything? Going back into my solution, I created a field with the exact same logical name on the Contact entity, making it clear that this field should not be used. The data type, description etc. does not matter, so long as the logical name is the same as our field above:

6

 

Going back to our form and trying again, we see that this looks to have done the trick – our three records are now returning successfully when we use the control! 🙂

9

Another good thing about this is that the custom filter will also apply when the user clicks on ‘Look Up More Records’; although the Contact entity can still be selected, no records will be returned for obvious reasons.

I was glad this was ultimately possible to get working; however, to play devil’s advocate for a few moments, it is worth noting the following if you choose to set this up within your own environment:

  • Simply creating a new 1:N relationship between Account:Contact would be a more straightforward and less technical approach of achieving the above; you should review your requirements carefully and consider whether this approach would satisfy your objectives.
  • By adding form level JScript to this entity, you may start to impact on your form load times, particularly if you have lots of other functions running on the form or if you implement additional logic into your function.
  • It is arguably not best practice to have a field within CRM that is being used in a “hacky” manner, as outlined above. If you are happy to have such a field within your CRM environment, then you will need to ensure that you take a number of steps to clearly label this field and its purpose within the CRM. The last thing you want is for someone to delete it by accident.

If anyone else has found a better or alternative way of achieving the above, please let me know in the comments below!