Welcome to my CRM Blog

Welcome to my new redesigned Dynamics CRM Blog.  My intention is to keep this more up to date than I previously had.

Hopefully this site will become a useful source of information regarding Microsoft Dynamics CRM

Adding Icons and Tooltips to a subgrid column

Over the years I have seen many unsupported solutions for getting nice little icons in a sub grid.  Often, this requirement stems from needing some form of traffic light display, and has only been possible by some sort of unsupported customizations where you can manipulate the DOM.

Now, with Dynamics 365 (December 2016 Update) for both online and on premise, you can do this in a very easy way by following the guide linked at the bottom of this post.

Here is my experience with it so far.

Continue reading “Adding Icons and Tooltips to a subgrid column”

FormXML solution layering

When working with Managed Solutions, and layering them on top of each other, be mindful of one very important fact.

If you include an entity in a solution, that is also present in another solution, then the most recent solution to be installed in a system will take precedent over others when it comes to certain things such as entity forms.

For example, if you have solution A containing entity 1 and install it managed onto a server, and then later install solution B which also contains entity 1, then it’s solution B’s customisations that will be applied. If you later edit entity 1’s form within solution A and try and install it onto the server containing both solutions, the form customisations will not take.

You must always try and ensure, where possible, that each entity only exists in one solution. This would not apply if you were always going to be providing both solutions as a pair to be installed.

To safeguard, always attempt to create a new form in each solution so that customisations are carried out in each solutions specific form, and always try to avoid accidentally including entities in solutions where they don’t need to be as you can’t always roll them back due to dependancies.

Good news is that with CRM 2016, you can include only the bits you need in a solution making it easier to avoid these kind of layering issues.

Report Printing from Dynamics within a VDI or restricted environment

Sometimes when attempting to Print Reports from Dynamics you get an error due to an ActiveX control. It appears that when you click the Print button, it tries to download and install a CAB file from the MS SQL Server that has Reporting Services Installed.

This needs Admin rights for the install to be successful and in some environments, this is blocked.

The following link had some very useful information concerning this.

Client Side Printing – Silent Deployment of rsclientprint

I have performed the following test on a Citrix VDI.

1. Tried running report, clicked Print, IE tried to install a plugin which I cancelled.
2. Copied the DLL’s from the CAB file over to windows\system32
3. Registered the DLL
4. Tried running report, clicked Print, and it worked 

So, to fix this issue, all we need to do is copy the DLL’s over to windows\system32 , register the one dll, and printing problem solved.

Forcing Internet Explorer out of compatibility mode

While developing some web resources for Dynamics, and also an external Web Portal, I was struggling with getting Internet Explorer to display properly, specifically using older browser versions such as IE7,IE8 and IE9. If I listen closely, I can probably hear you say “use the latest version of IE, upgrade, update and be done”.  Well, if I were in a position to make that happen, I probably would, however, like a lot of people out there working for structured companies, their base “corporate” desktop install never has the latest version of anything on it.

After much googling, I discovered that there is a specific meta tag you can use that causes Internet Explorer to use the latest standards, thus preventing any display issues.

So, in my web resources, I have added the following to the HEAD section and it seems to do the job quite nicely.

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

For my web portal, I was able to achieve the same across all pages by adding the tag into the IIS settings. I used the GUI to do this, but I did notice that all it did was add it to the web.config file.

In IIS, within your web site/application, select the following option :

ieStandards1

And then add the following :

ieStandards2

The resulting web.config for the web site/application now contains the following :

<httpProtocol>
     <customHeaders>
           <add name="X-UA-Compatible" value="IE=edge,chrome=1" />
     </customHeaders>
</httpProtocol>

Setting up Microsoft Dynamics CRM 2011 on Windows Server 2012 R2

I had a requirement to install Microsoft Dynamics 2011 onto a new server containing Microsoft Windows Server 2012 R2 for a Live Deployment, after developing using CRM on a Windows 2008 R2 server.  When the install wouldn’t work, it was then that I realised that 2011 is not officially support on Windows Server 2012.  That was an unpleasant surprise.  After doing some research, I found the required information to get it to work, and I have tried to describe the steps below for future reference.

 Step 1 – Windows Server 2012 R2

After installing the Server itself, and making sure its fully updated, you will want to make sure to add the following roles:

  • File and Storage Services
  • Web Server (IIS)
  • NET Framework 3.5 Features
  • NET Framework 4.5 Features
  • Windows Search Service
  • Windows Identify Framework

Step 2 – Retrieve the required files

As you will be unable to use your ISO or CD of Microsoft Dynamics to install, you will need to download the latest setup files from Microsoft.

Download the latest Microsoft Dynamics CRM 2011 Server setup file

Go to http://www.microsoft.com/en-us/download/details.aspx?id=27822 to download the latest Microsoft Dynamics CRM Server setup file. This also contains the appropriate SSRS Data Connector Setup File.

Download the latest Microsoft Dynamics CRM 2011 E-mail Router setup file

Go to http://www.microsoft.com/en-us/download/details.aspx?id=27818 to download the latest Microsoft Dynamics CRM E-mail Router setup file.

Download the Microsoft Dynamic CRM 2011 Self-Healing Setup (SHS)

Go to Microsoft Update Catalog http://catalog.update.microsoft.com/v7/site/Search.aspx?q=crm%20setup and then download the appropriate Setup Patches.  Instructions on what to do with these are further below.

Download the Microsoft Exchange Server MAPI Client and Collaboration Data Objects 1.2.1

Go to http://www.microsoft.com/en-ie/download/details.aspx?id=36771 and then download the appropriate Setup file.

Download the Microsoft Online Services Sign-In Assistant

Go to http://www.microsoft.com/en-gb/download/details.aspx?id=28177 and then download the appropriate Setup file.

Download the Windows Live ID Sign-in Assistant 6.5

Go to http://www.microsoft.com/en-gb/download/details.aspx?id=15106 and then download the appropriate Setup file.

Download the Update Rollup 17 for Microsoft Dynamics CRM 2011

Go to http://www.microsoft.com/en-us/download/details.aspx?id=42672 to download Update Rollup 17 (or if there is a later version, then download that).

Step 3 – Configure your CRM Setup

  • Extract your main CRM setup files to a folder on your server’s hard disk, but don’t run it yet. For example, extract it to d:\setup
  • Run and extract the Self-Healing Setup file for the Server setup. Within the resulting folder you will see lots of similar files :
    svr2012scr1
  • Find the file for your language (English is 1033, so the file you need is en-server_kb2434455_amd64_1033_xxxxxxxxxxxxx.cab) and double click the file. Copy the file contained (extension msp) into the same folder that your CRM setup files are (so d:\setup). You could also remove all the extra characters from the end of the file if you wish.
  • Create a custom setup file called config.xml and place it in the same setup folder. The contents of the file are below :
    &lt;CRMSetup&gt;
    &lt;Server&gt;
    &lt;Patch update="true"&gt;d:\setup\Server_KB2434455_amd64_1033.msp&lt;/Patch&gt;
    &lt;/Server&gt;
    &lt;/CRMSetup&gt;
    

 

Step 4 – Run the setup

You can now run the setup from a command line by doing typing the following into a command prompt:

SetupServer.exe /config d:\setup\config.xml

This should then start the normal CRM installation routine. The checking for update step should show “Setup has finished downloading the update” to confirm that the setup has picked the SHS up correctly from the config file. After this, you can install CRM as normal.

Once it has fully installed, you must then run the appropriate latest update rollup to make sure your CRM instance is fully patched and compatible.

 Step 5 – Setup SSRS Data Connector for CRM 2011

I found that installing the SSRS was less problematic. I was installing to a separate Reporting Services Server, and I had setup an appropriate config file to incorporate the relevant SHS file into the setup procedure, but I couldn’t get the main setup to run.  It turned out that I was able to just install the SSRS Data Connector without any issue by default.

After its installed, make sure you run the Update Rollup 17.

 Step 6 – Setup the Email Router for Dynamics CRM 2011

 

The setup for the email router also did not require the SHS hack, but, there were other pre-requisites that I had to install before I could proceed. Once I found out what I had to install (the automatic download and install of the setup would not work) everything went smoothly.  Just install the following three components onto the server, and the Router setup should then work without any issue.

  • First, Install the Microsoft Exchange Server MAPI Client and Collaboration Data Objects 1.2.1.
  • Then install the Microsoft Online Services Sign-In Assistant
  • And finally, install the Windows Live ID Sign-in Assistant 6.5

With these three components installed, the router installation was a breeze.

 

Fixing a WCF Web Service for Cross Domain/Browser Support

I had an issue recently where I was attempting to make my CRM 2011 customisations cross browser compatible. The issue was when I was calling a Web Service via Jquery’s AJAX methods. It was working fine in Internet Explorer, but when I was trying to access it from a Web Resource within Chrome, I was getting an issue that was suggesting it was a cross domain problem.

Although the web service was running on the same host, as it was on a different port, it was being classed as a cross domain ajax call and Chrome was stopping it.

I spent a while trying to find out how to fix the client JavaScript assuming the issue was how I was calling it, and then I stumbled on some useful information about Web Services and a nice little piece of code to enable the web service to work perfectly, leaving the client alone.

The issue boiled down to some extra calls being made to gather OPTIONS around the safety of the request.

“OPTIONS” Request called as “Preflight Request” – “preflighted” requests first send an HTTP OPTIONS request header to the resource on the other domain, in order to determine whether the actual request is safe to send and this request expects appropriate headers saying that service is allowing to access the service as a Response

The fix is to add a global.asax file to the web service project and include the following code which handles this OPTIONS request. Build, and deploy and all of a sudden it was working fine in Chrome as well 🙂

protected void Application_BeginRequest(object sender, EventArgs e)
{          
    HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");          
    if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
    {               
        HttpContext.Current.Response.AddHeader("Cache-Control", "no-cache");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
        HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
        HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
        HttpContext.Current.Response.End();              
    }
}

Popup a new entity form with default values via JavaScript

One of the most useful features I have found recently was being able to use the Xrm object to create window popups for new entities.  I was still using the old way of creating a URL with all the correct parameters, but I didn’t realise that the Xrm.Utilities object now has some very handy functions to allow you to do this.  You can even set up some default properties so that some of the fields are already populated.

function CreateIncident() {    
   //get Account GUID and Name    
   var AccountGUID = Xrm.Page.data.entity.getId();    
   var AccountName = Xrm.Page.data.entity.attributes.get("name").getValue();    
   //define default values for new Incident record    
   var parameters = {};    
   parameters["title"] = "New customer support request";    
   parameters["casetypecode"] = "3";    
   parameters["customerid"] = AccountGUID;    
   parameters["customeridname"] = AccountName;    
   parameters["customeridtype"] = "account";    
   //pop incident form with default values    
   Xrm.Utility.openEntityForm("incident", null, parameters);
}

Customising the Activity Views (and other such views) via Plugins

RetrieveMultiplePluginfeatureThe Requirement

I had a requirement to display a list of activities, both open and closed on the dashboard. Simple enough I hear you say. Well, it was until I needed to include a few more fields to make the user experience better. I wanted to categorise the activities, so as well as showing the type of activity (email,phone,letter,task), I wanted to give them a short description that would make a lengthy list of activities easily readable.

The Idea

Upon looking at the activities, such as email and phone call, I noticed they had a system default field called category, of which it was a single line of text. As it was present on all of the activities, I thought I would just use it. After much time spent updating my workflows to populate these category fields with the appropriate tags, I was ready to update the view.

The Problem

Bugger, it was then that I realised that the category field was unique to each activity, and not stored on the activity pointer (the entity that you would normally use to list all types of activities in one view). The activity pointer had the subject and description fields that are shared across other activities (plus a few more), but not the category field. And to top it off, the activity pointer entity is not customisable. So, at that point I started to think about using workflows or JavaScript to just simply prepend the tags I wanted to use to the beginning of the subject line, but it soon became apparent that it was not going to be suitable as there would be too much involved with making sure manual updates to activities didn’t spoil my tagging.

I began the “google”.

The Solution

Eventually, I happened upon a possible solution using plugins. Initially I was thinking of using a create or update plugin to change the subject field. Got too confusing, but, there was light at the end of the tunnel. I began experimenting with a plugin for the RetrieveMultiple command. This is what provides data for views within Dynamics, and even the activity pointer entity can have a RetrieveMultiple plugin to intercept the data it is about to send back to the web browser allowing you to tinker with the data returned.

To begin with, using the CRM Developer Toolkit for Visual Studio 2012, I browsed to the Activity Entity in the CRM Explorer window, right clicked and selected Create Plugin. Below are the selections I made to set up the plugin.

RetrieveMultiplePlugin

After that, I just added the code (at the bottom of this post) to intercept the returned EntityCollection’s subject field, and alter it in such a way so that it has the activities category and direction fields within.

The below line gets the EntityCollection that is about to be returned to the clients Grid View from the plugins OutputParameters collection.

EntityCollection entityCollection = (EntityCollection)localContext.PluginExecutionContext.OutputParameters["BusinessEntityCollection"];

I then loop through every entity, find the category from the appropriate activity entity (the activitypointer id is the same id that is stored within the email entity for example), and then modify the activitypointer’s subject attribute to include the category.

It really is that simple.

Interesting, this could be used in all kind of situations where you need to alter the displayed views (perhaps hiding fields based on security roles, or encrypting/decrypting passwords for viewing etc). One of the key uses though may be to retrieve data from other entities to include in a view that may not be possible by just using fetchxml.

Below is the Execute code to include in the plugin to achieve what I have described.

 protected void ExecutePostActivityRetrieveMultiple(LocalPluginContext localContext)
        {
            if (localContext == null)
            {
                throw new ArgumentNullException("localContext");
            }
            if (localContext.PluginExecutionContext.OutputParameters.Contains("BusinessEntityCollection"))
            {
                EntityCollection entityCollection = 
                    (EntityCollection)localContext.PluginExecutionContext.OutputParameters["BusinessEntityCollection"];
                if (true)       // This could be some form of user role check
                {
                    if (entityCollection.Entities.Count > 0)
                    {
                        Entity ActivityEntity = null;
                        foreach (Entity entity in entityCollection.Entities)
                        {
                            if (entity.Contains("activitytypecode"))
                            {
                                string type = entity.Attributes["activitytypecode"].ToString();
                                if (type != "")
                                {
                                    // These Activities have a direction field to indicate outgoing or incoming
                                    if (type == "email" || type == "letter" || type == "phonecall") 
                                        
                                        ActivityEntity = localContext.OrganizationService.Retrieve(
                                            type, entity.Id, new ColumnSet("category", "directioncode"));
                                    else
                                        ActivityEntity = localContext.OrganizationService.Retrieve(
                                            type, entity.Id, new ColumnSet("category"));
                                    if (ActivityEntity.Attributes.Contains("category"))
                                    {
                                        if (ActivityEntity.Attributes.Contains("directioncode"))
                                        {
                                            entity.Attributes["subject"] = 
                                                ActivityEntity.Attributes["category"].ToString().PadRight(30) + " - " +
                                                ((bool)ActivityEntity.Attributes["directioncode"] ? "Outgoing" : "Incoming")
                                                + " - " + entity.Attributes["subject"];
                                        }
                                        else
                                        {
                                            entity.Attributes["subject"] = 
                                                ActivityEntity.Attributes["category"].ToString().PadRight(30) + " - " + 
                                                entity.Attributes["subject"];
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }