Chrome Extension: Site Highlighter

EDIT (30/12/16): I have unpublished the extension from Chrome store and retaining this post and the GitHub repo. Stylish is a better extension and offers more features. This extension was a good learning experience for me on how to develop Chrome extension.

Site Highlighter is a Chrome extension I developed to do only one task: change the background colour of an element or a tag. The element could be selected by classname or id. I will give you an example. CRM 2013 doesn’t support themes. But if you just want to differentiate the various environments (DEV/TEST/PROD) by the navigation bar colour, it is not possible without changing the css using the the CRM web application. This is easy to do in OnPremise, but is technically an unsupported change.

In this instance you can use my extension to change the navigation bar to a different colour. The ID you can to match is navBar. The screenshot below shows you the config menu of the extension.

Site Highlighter Options

After you set this up the extension will highlight the element by id navbar, with green colour. The source code for the extension is available at https://github.com/rajyraman/Site-Highlighter. You can install the extension from https://chrome.google.com/webstore/detail/site-highlighter/hembcpepmfkaigjalcajpjjbeimbhnnk

This is my first extension and I worked on it only for three days, including constant ALT+TABing the Chrome Extension developer documentation, and so it may be a little rough around the edges.

Request to people with expertise in Chrome extension Development/UX & Users: If you can give your thoughts on things that I am not doing correctly, it will be helpful for me to make improvements. Thank you.

Bookmarklet: Theme Colour Picker

Only of the features lacking in the current CRM Theme form is colour picker. I logged issue 1197446 in Connect regarding this. But, it seems this is not really a priority at the moment. You can use Guido’s CRM Theme Generator to get around this and it is really excellent, as it also gives you instant feedback on how the theme will look. But I still like to see a colour picker in the theme form. So, I developed a bookmarklet to do this.

Copy paste this code into your favorites/bookmarks bar and execute this when you have opened the theme form.

javascript: var contentPanel=$('#crmContentPanel > iframe');if(contentPanel&&contentPanel.length>0){var targetFrame=contentPanel[0].contentDocument;var Xrm=contentPanel[0].contentWindow.Xrm;Array.from(targetFrame.querySelectorAll('.ms-crm-ColorValueDirection')).forEach(function(d,i){var c=targetFrame.createElement('input');c.setAttribute('id',d.getAttribute('id')+'_colorpicker');c.setAttribute('type','color');c.setAttribute('style','display: block; width: 50px; height: 16px; right: -54px; top: 1px; position: absolute;');c.value=targetFrame.querySelectorAll('#'+d.getAttribute('id')+' span')[0].textContent;d.appendChild(c);});targetFrame.getElementById('tdAreas').addEventListener('change',function(e){var targetIdParts=e.target.id.split('_');if(targetIdParts[1]!=='colorpicker')return;var mainDiv=targetIdParts[0];targetFrame.querySelectorAll('#'+mainDiv+' span')[0].textContent=e.target.value;targetFrame.getElementById(mainDiv+'_colord').style.background=e.target.value;targetFrame.getElementById(mainDiv+'_i').value=e.target.value;Xrm.Page.getAttribute(mainDiv).setValue(e.target.value);},false);}
else
{alert('Cannot locate theme IFrame');} void 0;

Here in the unminified source.

var contentPanel = $('#crmContentPanel > iframe');
if (contentPanel && contentPanel.length > 0) {
	var targetFrame = contentPanel[0].contentDocument;
	var Xrm = contentPanel[0].contentWindow.Xrm;
	Array.from(targetFrame.querySelectorAll('.ms-crm-ColorValueDirection')).forEach(function(d,i){
		var c = targetFrame.createElement('input');
		c.setAttribute('id',d.getAttribute('id')+'_colorpicker');
		c.setAttribute('type','color');
		c.setAttribute('style','display: block; width: 50px; height: 16px; right: -54px; top: 1px; position: absolute;');
		c.value = targetFrame.querySelectorAll('#'+d.getAttribute('id')+' span')[0].textContent;
		d.appendChild(c);
	});

	targetFrame.getElementById('tdAreas').addEventListener('change', function(e) {
		var targetIdParts = e.target.id.split('_');
		if(targetIdParts[1] !== 'colorpicker') return;
		var mainDiv = targetIdParts[0];
		targetFrame.querySelectorAll('#'+mainDiv+' span')[0].textContent = e.target.value;
		targetFrame.getElementById(mainDiv+'_colord').style.background = e.target.value;
		targetFrame.getElementById(mainDiv+'_i').value = e.target.value;
		Xrm.Page.getAttribute(mainDiv).setValue(e.target.value);
	},false);
}
else
{
	alert('Cannot locate theme IFrame');
}

Here is how it looks after you have executed the bookmarklet.

Colour Picker

I have tested this only in Firefox 43 and Chrome 47.

EDIT (27/01/16): Fixed hardcoded navbarbackgroundcolor in getAttribute. Apologies.

Critical bug with Full Text search

Most of the cool new features unveiled by Microsoft, are available in CRMOnline only. But, full text search is one of the features that is OnPremise only. It was made available with CRM2015 Update 0.1. By default, this is turned off. In order to turn on this feature you’ll have to head over to “Settings” area and select “Yes” next to “Enable full-text search for Quick Find”

Full Text Search

While testing out this feature in our DEV environment, I uncovered, what I consider, a major bug with the feature. The bug is this:

If you have turned on full text search, you lose the ability to alter the length of the text fields on any customisable entity.

If this OK in your case, you have nothing to worry. For others, read on.

Replication steps

  1. Use the query below to identify the full text indexes and the entity text fields
    SELECT
      b.name AS [Table Name],
      c.name AS [Index Name],
      d.name AS [Entity Name],
      e.Name AS [Text Attribute]
    FROM sys.fulltext_indexes a
    INNER JOIN sys.objects b
      ON a.object_id = b.object_id
    INNER JOIN sys.indexes c
      ON c.object_id = b.object_id
      AND c.index_id=a.unique_index_id
    INNER JOIN EntityAsIfPublishedLogicalView d
      ON d.basetablename = b.name
    INNER JOIN AttributeAsIfPublishedLogicalView e
      ON e.EntityId = d.EntityId
    WHERE d.iscustomizable = 1
    AND e.AttributeTypeId = '00000000-0000-0000-00AA-11000000001E'
    AND e.IsLogical = 0
    AND e.IsCustomizable = 1
    ORDER BY d.name, e.Name
    
  2. Try to increase of decrease the length of any of these fields from the entity customisation area. If you try to save the attribute after increasing or decreasing the length, you will get  a generic SQL exception.

SQL Error

Our test environment, that doesn’t have full text search enabled, doesn’t suffer from this issue. I was thinking of manually deleting the full text index from the base table and then updating the attribute length, but I didn’t do so, as I haven’t fully analysed the impact of doing this.

The downloaded error file doesn’t help to identify the root cause of this issue.

Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]:
System.Data.SqlClient.SqlException: Microsoft Dynamics CRM has experienced an error. Reference number for administrators or support: #85B87978Detail:
<OrganizationServiceFault xmlns:i=”http://www.w3.org/2001/XMLSchema-instance&#8221; xmlns=”http://schemas.microsoft.com/xrm/2011/Contracts”&gt;
<ErrorCode>-2147220970</ErrorCode>
<ErrorDetails xmlns:d2p1=”http://schemas.datacontract.org/2004/07/System.Collections.Generic&#8221; />
<Message>System.Data.SqlClient.SqlException: Microsoft Dynamics CRM has experienced an error. Reference number for administrators or support: #85B87978</Message>
<Timestamp>2016-01-25T10:54:39.1831237Z</Timestamp>
<InnerFault i:nil=”true” />
<TraceText i:nil=”true” />
</OrganizationServiceFault>

Cause

CRM wants to drop and recreate the full text index on the entity base table, when the length of any text field in that entity is updated. When it tries to do this, SQL throws an error.

FullTextQuery

FullTextError

So, my recommendation at this stage is not to use this feature, until this bug is resolved in the upcoming updates.

 

Backing up plugin/workflow assemblies

One of the ways to quickly backup the plugin/assemblies that are stored in the MSCRM database is to use the Assembly Recovery Tool that comes with XrmToolBox. Recently, I had a situation where I couldn’t back up the assembly this way, as one of the assembly was huge and the OrganizationService was experiencing some latency issues, resulting in a slow performance of the Assembly Recovery Tool.

I followed the method below to quickly backup the assemblies straight from the database.

  1. Install CShell. I prefer this over Linqpad for quickly running C# snippets because you get intellisense for free and it also has a REPL window.
  2. Run the query which is below in SQL Server Management Studio, against the MSCRM database
    SELECT [Name],[Content] FROM dbo.PluginAssemblyBase WHERE IsHidden=0
    
  3. Right click on the result and choose “Save Result As” from the context menu and specify the file name and the location
    SSMSResults
  4. Run the code below in the CShell scratchpad
    var pluginCsv = System.IO.File.ReadAllLines(@"[FULL PATH OF THE SAVED CSV]");
    foreach(var l in pluginCsv)
    {
    	var content = l.Split(',');
    	var assembly = Convert.FromBase64String(content[1]);
    	System.IO.File.WriteAllBytes(string.Concat(@"[OUTPUT PATH FOR THE ASSEMBLY]",content[0],".dll"),assembly);
    }
    

All the assemblies should now be saved to the specified folder. You can use this technique to restore assemblies from database backups of the MSCRM database.