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.

This is the final post in my 5 part series focusing on the practical implications surrounding the General Data Protection Regulation (GDPR) and how some of the features within Dynamics CRM/Dynamics 365 for Enterprise (CRM/D365E) can be utilised to smooth your organisations transition towards achieving compliance with the regulation. In this week’s post, we will be delving deep into the murky world of Subject Access Requests (SAR’s) (a process that already exists within existing E.U. Data Protection legislation), some of the changes that GDPR brings into the frame and the capabilities of the Word Template feature within CRM/D365E in expediting these requests as they come through to your organisation.

All posts in the series will make frequent reference to the text (or “Articles”) contained within Regulation (EU) 2016/679, available online as part of the Official Journal of the European Union – a particularly onerous and long-winded document. If you are based in the UK, you may find solace instead by reading through the ICO’s rather excellent Overview of the General Data Protection Regulation (GDPR) pages, where further clarification on key aspects of the regulation can be garnered.

Before jumping into the fun stuff, it’s useful to first set out the stall of what SAR’s are and to highlight some of the areas to watch out for under GDPR

A SAR is a mechanism through which an individual can request all information that a business or organisation holds on them. Section 7 of the UK’s Data Protection Act 1998 sets out the framework for how they operate and they are applicable to a wide variety of contexts – from requesting details from an Internet Service Provider regarding your account through to writing to an ex-employer to request what details of yours they hold on file. The types of information covered under a SAR can be quite broad:

  • Documents containing personal details
  • Emails
  • Call Recordings
  • Database Records

The effort involved in satisfying a SAR can be significant, typically due to the amount of information involved, and time will need to be put aside compiling everything together. You will also need to ensure certain types of information are redacted too, to prevent against an inadvertent data breach by revealing other data subjects details. It is for these reasons why SAR’s are typically seen as the bane of IT support personnel’s existences!

Be Aware Of The Implications Of Ignoring A SAR

Article 12 provides a broad – but nonetheless concerning – consequence should you choose to disregard or not process a SAR within the appropriate timeframes:

If the controller does not take action on the request of the data subject, the controller shall inform the data subject without delay and at the latest within one month of receipt of the request of the reasons for not taking action and on the possibility of lodging a complaint with a supervisory authority and seeking a judicial remedy.

Under current guidelines issued by the ICO for the Data Protection Act, the type of enforcement action include being mandated to process a SAR via a court order and even compensation for the data subject, if it can be proven that the individual has suffered personal damage through your lack of action. Whilst GDPR makes it unclear at the stage whether these consequences will remain the same or beefed up, organisations can make an assumption that there will be some changes under the new state of play, particuarly given that enforcement actions have been developed significantly in other areas (e.g. data breaches).

Overrall, SAR’s remain largely the same under GDPR, but there are a few subtle changes that you should make note of:

  • Most organisations currently will charge an “administration fee” for any SAR that is sent to them. GDPR does not specifically mandate that organisations can levy this charge anymore, so it can be inferred that they must now be completed free of charge. An organisation can, however, charge a “reasonable fee” if the data subject requests additional copies of the data that has already been sent to them (Article 15) or if requests are deemed to be “manifestly unfounded or excessive” (Article 12).
  • All information requested as part of an SAR must now be supplied within 1 month (as opposed to 40 days under existing legislation) of the date of the request. This can be extended to a further 2 months, subject to the organisation in question informing the data subject of the extension and the reason for the delay. Delays should only be tolerated in instances where the “complexity and number of the requests” exceeds normal situations (Article 12).
  • Organisations are within their right to request documentary evidence that the individual who has sent the SAR is the person they claim to be, via official identification or similar. This is useful in two respects: it enables an organisation to mitigate the risk of a potential data breach via a dishonest SAR and also affords the organisation additional time to process the request, as it can be inferred that the request can only be reasonably processed once the individual’s identity is confirmed.

The ability to expedite SAR’s in an efficient and consistent manner becomes a significant concern for organisations who are aiming to achieve GDPR compliance. But if you are using CRM 2016 or later, then this process can be helped along by a feature that any application user can quickly get to grips with – Word Templates

This feature, along with Excel Templates, is very much geared towards bridging the gap for power users wanting to generate reports for one or multiple record types, without having to resort to more complex means (i.e. SQL Server Reporting Services reports). I looked at the feature a while back on the blog, and it is very much something I now frequently jump to or advise others to within the application; for the simple reasons that most people will know how to interact with Word/Excel and that they provide a much easier means of accessing core and related entity records for document generation purposes.

To best understand how Word Templates can be utilised for SAR’s, consider the following scenario: ABC Company Ltd. use D36E as their primary business application system for storing customer information, using the Contact entity within the application. The business receives a SAR that asks for all personal details relating to that person to be sent across via post. The basic requirements of this situation are twofold:

  • Produce a professional response to the request that can then be printed onto official company stationary.
  • Quickly generate all field value date for the Contact entity that contain information concerning the data subject.

Both requirements are a good fit for Word Templates, which I will hopefully demonstrate right now 🙂

In true Art Attack style, rather than go through the process of creating a Word Template from scratch (covered by my previous blog post above), “here’s one I made earlier” – a basic, unskinned template that can be uploaded onto CRM/D365E via the Settings -> Templates -> Document Templates area of the application:

Subject Access Request Demo – Contact

When this is uploaded into the application and run against a sample record, it should look similar to the below:

Once deployed, the template can then be re-used across multiple record types, any future SAR’s can be satisfied in minutes as opposed to days and (hopefully) the data subject concerned is content that they have received the information requested in a prompt and informative manner.

Thanks for reading and I hope that this post – and the others in the series – have been useful in preparing your for GDPR and in highlighting some excellent functionality contained within CRM/D365E. Be sure to check out the other posts in the series if you haven’t done so already using the links below and do please leave a comment if you have any questions 🙂

Part 1: Utilising Transparent Database Encryption (TDE)

Part 2: Getting to Grips With Field Security Profiles

Part 3: Implementing & Documenting A Security Model

Part 4: Managing Data Retention Policy with Bulk Record Deletion

I was recently involved in deploying my first ever Office 365 Group. I already had a good theoretical understanding of them, thanks to the curriculum for the Business Applications MCSA, but I had not yet seen how they perform in action. The best way of summing them up is that they are, in effect, a distribution group on steroids. As well as getting a shared mailbox that can be used for all communications relating to the group’s purpose, they also support the following features:

  • Shared Calendar
  • SharePoint Document Site
  • Shared OneNote document
  • Shared Planner

In a nutshell, they can be seen as an excellent vehicle for bringing together the diverse range of features available as part of your Office 365 subscription. What helps further is that they are tightly integrated as part of the tools that you likely already use each day – for example, they can be accessed and worked with from the Outlook desktop client on and Web Access (OWA) portal.

Given that this feature is a very Office 365 centric component, the natural question emerges as to why an exam for Dynamics CRM/Dynamics 365 for Enterprise (CRM/D365E) would want to test your knowledge of them. Since the release of Dynamics CRM 2016 Update 1, you now have the option of integrating Office 365 Groups with the application, to provide a mechanism for easily working with groups from within the CRM/D365E web interface, effectively providing a “bridge” for non-CRM/D365E users who are using Office 365.

You may be pleased to hear that the steps involved in getting setup with Office 365 Groups in CRM/D365E are remarkably straightforward. Here’s a step-by-step guide on how to get up and running with this feature within your business:

Microsoft provides a managed solution that contains everything you need to get going with Office 365 Groups, and this is made available as a Preferred Solution. These are installed from the Dynamics 365 Administration Center by navigating to your instance, selecting the little pen icon next to Solutions and clicking on the Office 365 Groups record on the list that is displayed:

Click on the Install button and then accept the Terms of Service – as Office 365 Groups creates an intrinsic link between your CRM/D365E and Office 365 tenant, it is only natural that data will need to be shared between both, so there are no major concerns in accepting this:

The solution will take a couple of minutes to install, and you can safely refresh the window to monitor progress. Once installed, the Settings sitemap area will be updated with a new button – Office 365 Groups:

Clicking into this will navigate you to the Office 365 Groups Integration Settings page, which allows you start configuring the entities you wish to use to utilise with Office 365 Groups:

For reference purposes, the default out of the box entities that can be used with this feature are as follows:

  • Account
  • Competitor
  • Contact
  • Contract
  • Case
  • Invoice
  • Lead
  • Opportunity
  • Product
  • Quote
  • Sales Literature

You may be wondering if it is possible to enable additional entities for use with Office 365 Groups. At the time of writing, only the system entities recorded above and custom entities can be used with Office 365 Groups.

Now that we know how to get CRM/D365E setup for Office 365 Groups, let’s look at how it works when set up for the Account entity:

Going back to the Office 365 Groups Integration Settings (if you have closed it down), click on the Add entity button to enable a drop-down control, containing a list of the entities referenced above. Select Account and, when you are ready to proceed, click Publish all to enable this entity for Office 365 Groups functionality:

For this example, the Auto Create button is left blank. I would recommend that this setting is always used, so as to prevent the creation of unnecessary Office 365 Groups, that may get named incorrectly as a consequence (you’ll see why this has the potential to occur in a few moments).

Once enabled, when you navigate to an existing Account record, you will see a new icon on the Related Records sitemap area:

After clicking on this, you are then asked to either Create a new group – with the ability to specify its name – or to Search for an existing group. The second option is particularly handy if you have already been using Office 365 Groups and wish to retroactively tie these back to CRM/D365E:

For this example, we are going to create a new group. The process can take a while (as indicated below) so now may be a good opportunity to go make a brew 🙂

Leaving the screen open will eventually force a refresh, at which point your new group will appear, with all the different options at your disposal:

With your group now up and running, you can start uploading documents, configure the shared calendar and fine-tune the group’s settings to suit your purposes. Here are some handy tips to bear in mind when using the group with CRM/D365E:

  • Just because the group is linked up with CRM/D365E doesn’t mean that you have to be a user from this application to access the group. This is one of the great things about utilising Office 365 Groups with CRM/D365E, as standard Office 365 users can join and work with the group without issue. The only thing you have to remember is that the Office 365 user has to have the appropriate license on Office 365 – as indicated by Microsoft, any subscription that gives a user an Exchange Online mailbox and SharePoint Online access will suffice.
  • Remember that the ConversationsNotebook and Documents features are not in any way linked with the equivalent CRM/D365E feature. For example, any Conversation threads will not appear within the Social Pane as an activity; you will need to navigate to the Office 365 Group page to view these.
  • Utilising Office 365 Groups as an end-user requires that you have the appropriate security role access. If you do not, then you may be greeted with the following when attempting to open an Office 365 Group within the application:

That’s right – a whole heap of nothing! 🙂 To fix this, you will need to go into the users Security Role and ensure that they have Organization-level privilege on the ISV Extensions privilege, as indicated below:

Conclusions or Wot I Think

Office 365 Groups present a natural choice when working as part of large-scale teams or projects – especially when they are internally based. They can also be a good fit for when you wish to liaise with 3rd party organisations, thanks to the ability to grant Guest access to external accounts. Having the ability to then tie these groups back within CRM/D365E is useful, but I do wonder whether they are a good match for all of the record types that Microsoft suggests in the list above. Certainly, Account records are a justifiable fit if you are working with an organisation to deliver continuous services or multiple projects. I doubt highly, however, that you want to go to the trouble of creating a shared document repository for a new Lead record right from the bat – particularly if your CRM/D365E deployment is more focused towards B2C selling. You may be tempted to over-excitedly roll out Office 365 Groups carte blanche across your CRM/D365E deployment, but I would caution against this. Don’t forget that the creation of a new Office 365 Group will result in additional overhead when managing your Exchange Online mailbox lists and SharePoint sites, as well as having long-term storage implications for the latter. Acting prudently, you can identify a good business case for enabling specific entities for use with Office 365 Groups and ensure that you manage your entire Office 365 deployment in the most effective manner possible.

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.

When you have spent any length of time working with Dynamics CRM Online/Dynamics 365 for Enterprise (D365E) data programmatically, you become accustomed to how Option Set, State and Status Reason values are presented to you in code. To explain, the application does not store your Option Set value display names within the SQL Server Entity tables; rather, the Option Set Value that has been specified alongside your Label is what is stored as an integer value. That is why you are always mandatorily prompted to provide both values within the application:

The following benefits are realised as a result of how this is setup:

That being said, when working with these field types in code, you do always have to have the application window open or a list of all Labels/Values to hand so that you don’t get too confused… 🙂

I have previously extolled the virtues of the Data Export Service on the blog, and why you should consider it if you have basic integration requirements for your CRM/D365E deployment. One area in which it differs from other products on the market is how it handles the field types discussed above. For example, when exporting data to a SQL database via Scribe Online, new columns are created alongside that contain the “Display Name” (i.e. label value) that correspond to each Option, Status and Status Reason Label. So by running the following query against a Scribe export database:

SELECT DISTINCT statecode, statecode_displayname
FROM dbo.account

We get the best of both worlds – our underlying statecode value and their display names – all in 2 lines of code:

This is a big help, particularly when you are then using the data as part of a report, as no additional transformation steps are required and your underlying SQL query can be kept as compact as possible.

The Data Export Service differs from the above in an understandable way, as display name values for Status, Status Reason and Option Set column values are instead segregated out into their own separate table objects in your Azure SQL database:

OptionSetMetadata

GlobalOptionSetMetadata

StateMetadata

StatusMetadata

Why understandable? If you consider how the application can support multiple languages, then you realise that this can also apply to metadata objects across the application – such as field names, view names and – wouldn’t you have guessed it – Labels too. So when we inspect the OptionSetMetadata table, we can see that the table structure accommodates the storing of labels in multiple languages via the LocalizedLabelLanguageCode field:

Unlike the Scribe Online route above (which I assume only retrieves the Labels that correspond to the user account that authenticates with CRM/D365E), the Data Export Service becomes instantly more desirable if you are required to build multi-language reports referencing CRM/D365E application data.

The issue that you have to reconcile yourself with is that your SQL queries, if being expressed as natively as possible, instantly become a whole lot more complex. For example, to achieve the same results as the query above, it would have to be adapted as follows for the Data Export Service:

SELECT DISTINCT statecode, LocalizedLabel
FROM dbo.account
 LEFT JOIN dbo.StateMetadata
  ON 'account' = EntityName
  AND statecode = [State]
  AND '1033' = LocalizedLabelLanguageCode

The above is a very basic example, but if your query is complex – and involves multiple Option Set Values – then you would have to resort to using Common Table Expressions (CTE’s) to accommodate each potential JOIN required to get the information you want.

In these moments, we can look at some of the wider functionality provided as part of SQL Server to develop a solution that will keep things as simple as possible and, in this particular instance, a user-defined function is an excellent candidate to consider. These enable you to perform complex operations against the database platform and encapsulate them within very simply expressed objects that can also accept parameters. The good thing about functions is that they can be used to return table objects and scalar (i.e. single) objects.

Using a scalar function, we can, therefore, remove some of the complexity behind returning Option Set, Status and Status Reason labels by creating a function that returns the correct label, based on input parameters received by the function. You could look at creating a “master” function that, based on the input parameters, queries the correct Metadata table for the information you need; but in this example, we are going to look at creating a function for each type of field – Status, Status Reason, Option Set and Global Option Set.

To do this, connect up to your Data Export Service database and open up a new query window, ensuring that the context is set to the correct database. Paste the following code in the window and then hit Execute:

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

--Create Function to return Global Option Set Labels

CREATE FUNCTION [dbo].[fnGetGlobalOptionSetLabel]
(
	@GlobalOptionSetName NVARCHAR(64), --The logical name of the Global Option Set
	@Option INT, --The option value to retrieve
	@LanguageCode INT --The Language of the label to retrieve. English is 1033. Full list of support languages (Correct as of June 2015) can be found here: https://abedhaniyah.blogspot.co.uk/2015/06/list-of-supported-languages-by.html
)
RETURNS NVARCHAR(256)
AS
BEGIN

	DECLARE @Label NVARCHAR(256);
	DECLARE @RecordCount INT = (SELECT COUNT(*) FROM dbo.GlobalOptionSetMetadata WHERE OptionSetName = @GlobalOptionSetName AND [Option] = @Option AND LocalizedLabelLanguageCode = @LanguageCode);
	IF @RecordCount = 1
		SET @Label = (SELECT TOP 1 LocalizedLabel FROM dbo.GlobalOptionSetMetadata WHERE OptionSetName = @GlobalOptionSetName AND [Option] = @Option AND LocalizedLabelLanguageCode = @LanguageCode);
	ELSE
		SET @Label = CAST('An error has occurred. Could not obtain label for Global Option Set field ' + @GlobalOptionSetName AS INT);
	RETURN @Label;

END

GO

--Create Function to return Option Set Labels

CREATE FUNCTION [dbo].[fnGetOptionSetLabel]
(
	@EntityName NVARCHAR(64), --The Entity logical name that contains the Option Set field
	@OptionSetName NVARCHAR(64), --The logical name of the Option Set field
	@Option INT, --The option value to retrieve
	@LanguageCode INT --The Language of the label to retrieve. English is 1033. Full list of support languages (Correct as of June 2015) can be found here: https://abedhaniyah.blogspot.co.uk/2015/06/list-of-supported-languages-by.html
)
RETURNS NVARCHAR(256)
AS
BEGIN

	DECLARE @Label NVARCHAR(256);
	DECLARE @RecordCount INT = (SELECT COUNT(*) FROM dbo.OptionSetMetadata WHERE EntityName = @EntityName AND OptionSetName = @OptionSetName AND [Option] = @Option AND LocalizedLabelLanguageCode = @LanguageCode);
	IF @RecordCount = 1
		SET @Label = (SELECT TOP 1 LocalizedLabel FROM dbo.OptionSetMetadata WHERE EntityName = @EntityName AND OptionSetName = @OptionSetName AND [Option] = @Option AND LocalizedLabelLanguageCode = @LanguageCode);
	ELSE
		SET @Label = CAST('An error has occurred. Could not obtain label for Option Set field ' + @OptionSetName AS INT);
	RETURN @Label;

END

GO

--Create Function to return Status Labels

CREATE FUNCTION [dbo].[fnGetStateLabel]
(
	@EntityName NVARCHAR(64), --The Entity logical name that contains the Status field
	@State INT, --The Status option value to retrieve
	@LanguageCode INT --The Language of the label to retrieve. English is 1033. Full list of support languages (Correct as of June 2015) can be found here: https://abedhaniyah.blogspot.co.uk/2015/06/list-of-supported-languages-by.html
)
RETURNS NVARCHAR(256)
AS
BEGIN

	DECLARE @Label NVARCHAR(256);
	DECLARE @RecordCount INT = (SELECT COUNT(*) FROM dbo.StateMetadata WHERE EntityName = @EntityName AND [State] = @State AND LocalizedLabelLanguageCode = @LanguageCode);
	IF @RecordCount = 1
		SET @Label = (SELECT TOP 1 LocalizedLabel FROM dbo.StateMetadata WHERE EntityName = @EntityName AND [State] = @State AND LocalizedLabelLanguageCode = @LanguageCode);
	ELSE
		SET @Label = CAST('An error has occurred. Could not obtain State label for entity ' + @EntityName AS INT);
	RETURN @Label;

END

GO

--Create Function to return Status Reason Labels

CREATE FUNCTION [dbo].[fnGetStatusLabel]
(
	@EntityName NVARCHAR(64), --The Entity logical name that contains the Status Reason field
	@Status INT, --The Status Reason option value to retrieve
	@LanguageCode INT --The Language of the label to retrieve. English is 1033. Full list of support languages (Correct as of June 2015) can be found here: https://abedhaniyah.blogspot.co.uk/2015/06/list-of-supported-languages-by.html
)
RETURNS NVARCHAR(256)
AS
BEGIN

	DECLARE @Label NVARCHAR(256);
	DECLARE @RecordCount INT = (SELECT COUNT(*) FROM dbo.StatusMetadata WHERE EntityName = @EntityName AND [Status] = @Status AND LocalizedLabelLanguageCode = @LanguageCode);
	IF @RecordCount = 1
		SET @Label = (SELECT TOP 1 LocalizedLabel FROM dbo.StatusMetadata WHERE EntityName = @EntityName AND [Status] = @Status AND LocalizedLabelLanguageCode = @LanguageCode);
	ELSE
		SET @Label = CAST('An error has occurred. Could not obtain Status label for entity ' + @EntityName AS INT);
	RETURN @Label;

END

GO

This will then go off and create the functions listed in code, which should then show up under the Programmability folder on your SQL database:

For those who are unsure at what the SQL code is doing, it first attempts to determine if only 1 Label can be found for your appropriate field type, based on the parameters provided. If it is successful, then a value is returned; otherwise, the CAST function is designed to force an error to return back to the caller to indicate that none or more than 1 Option Set value was found. In most cases, this would indicate a typo in the parameters you have specified.

As with anything, the best way to see how something works is in the practice! So if we again look at our previous examples shown in this post, we would utilise the dbo.fnGetStateLabel function as follows to return the correct label in English:

SELECT DISTINCT statecode, dbo.fnGetStateLabel('account', statecode, 1033) AS statecode_displayname
FROM dbo.account

With our results returning as follows:

Now we can expose this through our reports and not worry about having to do any kind of transformation/lookup table to get around the issue 🙂

Attempting to keep things as simple as possible by encapsulating complex functionality into simply and clearly expressed functions is an excellent way of ensuring that code can be kept as streamlined as possible, and also ensures that other colleagues can accomplish complex tasks, even if they do not have in-depth knowledge of Transact-SQL.