Convert Personal View to System View

When you design a query from the “Advanced Find” window, you can save the query for future use. I call this “Personal View”, but the official name for this is “Saved View”. This view is only visible to the person who saved the view (unless it is shared/reassigned).

As more personal views get created, it becomes an issue during migration, as these are not transported in the solution xml. You can of course use Solution Extender to copy this across, but I would like to do this right from the Advanced Find window. Apart from data migration, another scenario where you might find the need to create a system view from a personal view, is when you are constantly sharing views to a large number of people of group. When you are doing this, it is good time to actually make this a system view.

With these scenarios in mind, I have developed a solution that simplifies the process of creating a system view from a personal view. After installing the managed solution, you will see a new button called “Promote to System View” in the advanced find, when you switch to the “Saved Views” tab.

screenshot

To create a system view from the personal view, simply choose the views that need to be converted, and click the “Promote to System View” button. A message will be displayed after the conversion is complete. Once you refresh the window, you should be able to see the newly created system view(s).

Please log any issues/feedback/feature request in the github repo -> https://github.com/rajyraman/Personal-View-to-System-View/issues

You can download the managed solution from https://github.com/rajyraman/Personal-View-to-System-View/releases

Plugin Integration Tests using FakeXrmEasy

XrmUnitTest and FakeXrmEasy are two testing frameworks that are specifically targeted towards Dynamics CRM/Dynamics 365. It is possible to do both unit test as well as integration tests using both these frameworks. In this first post, I would like to cover FakeXrmEasy. Here is a sample plugin code that I would like to test:

using Microsoft.Xrm.Sdk;
using System;
using Microsoft.Crm.Sdk.Messages;

namespace SamplePlugin
{
    public class LastNameUpperCasePlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = factory.CreateOrganizationService(context.UserId);

            try
            {
                Entity entity = (Entity)context.InputParameters["Target"];
                //not required. Just added to demonstrate that we are connecting to a real crm org service
                var response = service.Execute(new WhoAmIRequest());

                var lastName = entity.GetAttributeValue<string>("lastname");
                if (!string.IsNullOrEmpty(lastName))
                {
                    entity["lastname"] = lastName.ToUpper();
                }
            }
            catch (Exception e)
            {
                throw new InvalidPluginExecutionException(e.Message);
            }
        }
    }
}

In order to test this plugin, we have to create a unit test project. Here are the steps for it:

  1. Create a unit test  project from the existing plugin solution by right clicking and choose Add->New Project->Visual C#->Test->Unit Test Project
  2. Use nuget to add the correct FakeXrmEasy reference to the test project. In this example I am using “FakeXrmEasy.365” as I am connecting to a Dynamics 365 instance online. Refer https://www.nuget.org/profiles/jmontana to find out all the different versions that are relevant to your CRM version.
  3. Create a App.config file, if it doesn’t already exist. Here is my connection string. Refer https://msdn.microsoft.com/en-us/library/mt608573.aspx to find the correct format of the connection string for you crm instance.connection-string

Below is my test for the plugin

using System;
using System.Collections.Generic;
using System.Web.Configuration;
using FakeXrmEasy;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Xrm.Sdk;

namespace SamplePlugin.Test
{
	[TestClass]
	public class LastNamePluginUnitTests
	{
		[TestMethod]
		public void Should_Set_LastName_In_UpperCase()
		{
			var context = new XrmRealContext
			{
				ProxyTypesAssembly = typeof(LastNameUpperCasePlugin).Assembly,
				ConnectionStringName = "CRMOnline"
			};
			var executionContext = context.GetDefaultPluginContext();
			var target = new Entity("contact")
			{
				["lastname"] = "Power",
				Id = Guid.NewGuid()
			};
			executionContext.MessageName = "Create";
			executionContext.Stage = 20;
			executionContext.PrimaryEntityId = target.Id;
			executionContext.PrimaryEntityName = target.LogicalName;
			executionContext.InputParameters = new ParameterCollection
			{
				new KeyValuePair<string, object>("Target", target)
			};
			context.ExecutePluginWith<LastNameUpperCasePlugin>(executionContext);
			Assert.AreEqual("POWER",
				((Entity) executionContext.InputParameters["Target"]).GetAttributeValue<string>("lastname"));
		}
	}
}

In this test I am creating a scenario where the plugin is running pre-create. You can change to add pre/post entity images or target a different message and stage. If we debug our integration test, we can see that we are connected to the real org service.

Debugger.png

I have used latebound entity in this example, but you can use the same code with early bound entities generated using crmsvcutil or Early Bound Generator.

You can use a similar approach for testing custom workflow assemblies as well. In that case you would be using “GetDefaultWorkflowContext” instead of “GetDefaultPluginContext” to get the execution context.

Footnote: Technically this test could have been better written as an unit test instead of a integration test. But the purpose of this post is to demonstrate the points below:

  1. How to build a fake plugin execution context
  2. How to use connection strings to create a real OrganizationService instance
  3. How to use the real OrganizationService with the fake plugin execution context

EDIT (22/11/2016): Thank you Jonas (@rappen) for pointing out the plugin stage should be pre-create for this to work. Updated context stage to 20.

Two useful LINQ Queries

In order to run these queries you’ll have to install LINQPad and LINQPad Driver for CRM.

Query 1 – Who created the entities?

It is not possible to look at an entity and find out who created it. You can however use the createdby on the SavedQuery (System View) to find out this information, as the System View is created the same time as the entity is created and also contains the createdby user information.

from q in
(from s in SavedQuerySet.AsEnumerable()
 where s.QueryType == 0
 group s by s.ReturnedTypeCode
into g
 select new
 {
	 g.Key,
	 Views = (from s in SavedQuerySet
			  where s.ReturnedTypeCode == g.Key && s.QueryType == 0
			  orderby s.CreatedOn
			  select new { s.CreatedOn, s.CreatedBy }).First()
 })
orderby q.Views.CreatedOn descending
select new { q.Key, CreatedOn = q.Views.CreatedOn.Value.ToLocalTime(), q.Views.CreatedBy.Name }

Entity creation query.png

Query 2 – All Plugins with message, stage, filtering attributes and entity

You can use this query to get a quick snapshot of all the plugins in the system.

from m in SdkMessageProcessingStepSet
join f in SdkMessageFilterSet on m.SdkMessageFilterId.Id equals f.SdkMessageFilterId
join s in SdkMessageSet on f.SdkMessageId.Id equals s.SdkMessageId
join p in PluginTypeSet on m.PluginTypeId.Id equals p.PluginTypeId
where f.IsCustomProcessingStepAllowed.Value
&& !m.IsHidden.Value
&& m.CustomizationLevel.Value == 1
select new { Message = s.Name, Rank = m.Rank.Value, Stage = m.Stage, StageName = m.FormattedValues["stage"], m.FilteringAttributes, p.AssemblyName, PluginName= p.Name, StepName = m.Name, StepDescription = m.Description, Status = m.StatusCode, StatusName = m.FormattedValues["statuscode"]}

plugins

Understanding Process Triggers and Business Rule internals

One of the less utilised/understood feature of Business Rule is Process Triggers. In this post, I will explain what a process trigger is and how you can use this in the context of business rule.

The Basics

Business Rule is basically a workflow that has a different UI compared to the standard workflow editor. You can quickly find all the business rules in your CRM instance by running this query.

Business Rules

Internals – How does Business Rules work

You can basically skip this part, if you are not interested in understanding the internals on how a business rules work. When you create a business rules you basically have all these components that make the business rules run seamlessly:

  1. The client side code that runs on the form
  2. The server side workflow defined in xaml
  3. Process trigger -> This dictates when the business rule logic should execute

When you design a business rule, it is automatically translated into a workflow xaml that executes on the server side and JavaScript code that executes on the client side.

Now, let us take a simple example of a business rule that sets the “Salutation”, when “Gender” is changed. Here is the business rule

Business Rule Definition

When you save this business rule, this is automatically translated into JavaScript, code that can run on the client side. Below is the JavaScript code that is generated by CRM, for this business rule:

function pbl_109af564df34e51180eac4346bc576e8() {
    try {
        var v0 = Xrm.Page.data.entity.attributes.get('gendercode');
        var v1 = Xrm.Page.data.entity.attributes.get('salutation');
        if (((v0) == undefined || (v0) == null || (v0) === "") || ((v1) == undefined || (v1) == null || (v1) === "")) {
            return;
        }
        var v2 = (v0) ? v0.getValue() : null ;
        if ((v2) === (1)) {
            v1.setValue('Mr');
        } else if ((v2) === (2)) {
            v1.setValue('Ms');
        }
    } catch (e) {
        Mscrm.BusinessRules.ErrorHandlerFactory.getHandler(e, arguments.callee).handleError();
    }
}

Below is the JavaScript code this calls the “pbl_109af564df34e51180eac4346bc576e8” function that contains the logic for the business rule.

Mscrm.BusinessRulesScript.Initialize = function() {
    Mscrm.BusinessRulesScript.AttributesOnChangeHandlers = {};
    Mscrm.BusinessRulesScript.ControlsOnClickHandlers = {};
    (function() {
        var onchangehandler = function() {
            pbl_109af564df34e51180eac4346bc576e8();
        }
        ;
        Mscrm.BusinessRulesScript.AttributesOnChangeHandlers['gendercode'] = onchangehandler;
        var attributeObject = Xrm.Page.data.entity.attributes.get('gendercode');
        if (attributeObject != null && attributeObject != undefined) {
            attributeObject.addOnChange(onchangehandler);
        }
    })();
    pbl_109af564df34e51180eac4346bc576e8();
};

From the above, triggering code we can see that the business rule is going to run when the form is opened, as the function “pbl_109af564df34e51180eac4346bc576e8” is called when the business rule is initiated. The function also executes when “Gender” is changed.

If you want to know what the generated JavaScript code for the business rule is, just get the “ClientData” field in the “Workflow” entity. You cannot get this field from Advanced Find. You can either use FetchXML Builder (a XrmToolBox tool) or LINQPad. Below is the fetchxml query, I used.

FetchXml

If you also get the “xaml” field on the workflow entity, you can see the markup server side workflow logic that will execute.

Process Trigger

Process Trigger dictates the events that will trigger the execution of business rule. There are three events:

  1. Load
  2. Change
  3. Save

“Load” and “Change” are the standard triggers when the business rule is created through the UI. “Save” is a special handler. It can only be set using the SDK and not through the UI. It behaves little differently compared to “Load”. The JavaScript code that is generated for “Save” is little different compared to the code that is generated for “Load”. Here is the LINQ query I used for getting process triggers for this workflow.

LINQPad

Now let us update the “load” trigger to “save”, so that the generated JavaScript for the business rule will run only on “Form Save” event and not on “Form Load”. Here is the simple snippet I ran to do this, after I got the ids of the process trigger in the previous LINQ Query. These process triggers are for the “load” events associated to our business rule. You have to deactivate the business rule, before you update the process trigger for the business rule.

Process Trigger Update

Now comes the important bit: Activate the business rule from the Advanced Find results (first screenshot). Don’t activate the business rule from the standard business rule window. If you activate the business rule from the standard UI, your process trigger will reset back to “load”

ActivateDont Activate

Now that the trigger is set to “Save”, lets compare the generated JavaScript code.

Compare Load and Save

As we can see, in the case of “save” process trigger, the client side business rule code runs only on “Save”. I like this, because I don’t want the business rule to run on “load” and confuse the user with “Unsaved changes” message. I will demonstrate this with a scenario.

Scenario: User opens a contact record, which doesn’t have the “Salutation” field set. Gender contains a valid value. The process trigger is “load”

Form OnLoad

As you can see from the above screenshot, the business rule ran immediately on form load, and set the Salutation to “Mr”. Hence, you have a unsaved changes message, on the bottom right. I am not very happy with this result because, it is not obvious to the user what changed and what caused the change. I want more control, so I want this rule to run only after “Save”. Now look at the same form, when the trigger is “Save”

Form OnSave

As you can see, the “Salutation” field is not set immediately. It will be set only

  1. When the form is saved OR
  2. When the “Gender” field is changed

One more thing: Every time when you deactivate and reactivate a business rule, new process triggers records are created, and so you have to get the correct ids when you update the “event” attribute.

I hope you can now understand the internals of business rule and how to use process trigger to control its behaviour.

References:

  1. TechNet: Create and edit business rules
  2. MSDN – Create or edit how business rules are initiated

 

 

Issues in debugging custom workflow assemblies

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

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

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

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

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

Issue 1: Cannot see the workflow step.

Blank Step

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

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

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

Issue 2: NullReferenceException

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

Null Reference.png

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

Workflow

Hope this helps with your debugging efforts.

Restricting the customer lookup

EDIT (07/03/2018)Updated the post for v9. In v9, you can use the supported approach of setEntityTypes and getEntityTypes. Refer https://docs.microsoft.com/en-us/dynamics365/customer-engagement/developer/clientapi/reference/controls/setentitytypes

EDIT (04/07/2017): After testing this in 8.2.1, I have simplified the code as getLookupTypes and setLookupTypes can be used straight from the lookup itself, instead of going through getLookupDataAttribute.

First off, I have tested this only in CRMOnline on 8.1.0 and 8.2.1 so this might not work for you if you are are in older version. You can now easily restrict the default entity of the lookup, if it links to multiple types. e.g. customer lookup. The script below is for attribute of type “customer”, but you can follow a similar approach for “partylist” as well.

These new functions are undocumented, so they are technically unsupported (only 8.x. 9.x is documented and supported). So, use this at your own risk. In the script below, I am restricting a field with schemaname “customerid” and of type “customer” to “contact” entity only.

JavaScript (9.x) – Supported

var lookup = Xrm.Page.getControl('customerid');

//check if multiple type dropdowns enabled for this lookup
if (lookup.getEntityTypes().length > 1) {
    lookup.setEntityTypes(['contact']);
}

JavaScript (8.x) – Unsupported

var lookup = Xrm.Page.getAttribute('customerid');

//check if multiple type dropdowns enabled for this lookup and it is not a partylist. For partylist we might want to select an account and a contact
if (lookup.getLookupTypes().length > 1
     && !lookup.getIsPartyList()) {
    lookup.setLookupTypes(['contact']);
}

Screenshots

LookupLookup Search Customer

You can use this script during form load and easily restrict the lookup type.

Bug: EntityReference, Create and latebinding

For big projects and large code sizes, it is recommended to use strongly typed classes when you are dealing with CRUD operations related to the CRM entities. You can use Early Bound Generator or “crmsvcutil” to do this. The advantages are compile type errors for mismatches types and intellisense. But if you are quickly testing out some code, latebinding is the easiest path, as it doesn’t involve the overhead of generating the classes. During this process, I discovered a bug.

Here is the code and I will explain the bug after that.

            CrmServiceClient crmSvc = new CrmServiceClient(ConfigurationManager.ConnectionStrings["CRMConnectionString"].ConnectionString);
            if (crmSvc.IsReady)
            {
                #region create contact with account entityreference

                var account = new Entity("account");
                account["name"] = "Test Account";
                account.Id = crmSvc.OrganizationServiceProxy.Create(account);
                Console.WriteLine($"Created account with id {account.Id}");

                Console.WriteLine("Creating contact with account as EntityReference");
                Console.WriteLine("----------------------------------------------------");

                var contact = new Entity("contact");
                contact["parentcustomerid"] = account.ToEntityReference();
                contact.Id = crmSvc.OrganizationServiceProxy.Create(contact);
                Console.WriteLine($"Created contact with id {contact.Id}");

                contact = crmSvc.OrganizationServiceProxy.Retrieve("contact", contact.Id, new ColumnSet("parentcustomerid"));
                Console.WriteLine($"Contact: Account Lookup {contact.GetAttributeValue<EntityReference>("parentcustomerid")?.Id}");

                #endregion

                #region update contact with account Guid

                Console.WriteLine("Updating contact with account as Guid");
                Console.WriteLine("----------------------------------------------------");
                try
                {
                    contact["parentcustomerid"] = account.Id;
                    crmSvc.OrganizationServiceProxy.Update(contact);
                    var updateContact = crmSvc.OrganizationServiceProxy.Retrieve("contact", contact.Id, new ColumnSet("parentcustomerid"));
                    Console.WriteLine($"Updated Contact: Account Lookup {updateContact.GetAttributeValue<EntityReference>("parentcustomerid")?.Id}");
                }
                catch (Exception e)
                {
                    Console.WriteLine($"Update Contact failed with Exception: {e.Message}");
                }

                crmSvc.OrganizationServiceProxy.Delete("contact", contact.Id);
                Console.WriteLine($"Deleted contact with id {contact.Id}");

                #endregion

                #region create contact with account Guid

                Console.WriteLine("\nCreating contact with account as Guid");
                Console.WriteLine("----------------------------------------------------");
                contact = new Entity("contact");
                contact["parentcustomerid"] = account.Id;
                //This should not succeed
                contact.Id = crmSvc.OrganizationServiceProxy.Create(contact);
                Console.WriteLine($"Created contact with id {contact.Id}");

                contact = crmSvc.OrganizationServiceProxy.Retrieve("contact", contact.Id, new ColumnSet("parentcustomerid"));
                Console.WriteLine($"Contact: Account Lookup {contact.GetAttributeValue<EntityReference>("parentcustomerid")?.Id}");

                crmSvc.OrganizationServiceProxy.Delete("contact", contact.Id);
                Console.WriteLine($"Deleted contact with id {contact.Id}");

                crmSvc.OrganizationServiceProxy.Delete("account", account.Id);
                Console.WriteLine($"\nDeleted account with id {account.Id}");

                #endregion

            }

Here is the result.

Guid Entityreference result

Even if you are using latebinding, CRM webservice respects type and doesn’t allow you to set an attribute to a type, that doesn’t match its definition, except in one case: You can set the EntityReference attribute to a Guid, and the code with execute without any exception. The good thing though is the fields is just set to null, and the Guid is ignored, but this a bug as you cannot do it with other types e.g. you can’t set an Optionset field to an int or a Currency field to a decimal.

I have logged this on Connect for verification. I experienced this behaviour in CRMOnline and CRM2015.

Connect issue -> https://connect.microsoft.com/site687/feedback/details/2740732/sdk-permits-setting-entityreference-attribute-to-guid-late-binding

Further reading:

MSDN: Create, retrieve, update, and delete (late bound)

 

Using Stylish to enhance CRM

EDIT (11/07/2018): Stylish has been acquired by SimilarWeb and it now is consider a spware -> https://robertheaton.com/2018/07/02/stylish-browser-extension-steals-your-internet-history/

DON’T USE STYLISH ANYMORE. Uninstall, if you already have it.

Use Stylus instead, as recommended in the blog post above.

Chrome: https://chrome.google.com/webstore/detail/stylus/clngdbkpkpeebahjckkjfobafhncgmne

Firefox: https://addons.mozilla.org/en-US/firefox/addon/styl-us/

What is Stylish?

Stylish is a browser extension available for both Firefox and Chrome. It helps you to alter the styles on any site that you don’t control. The change is localised i.e. it is visible only in your local machine.

How can I use this to enhance Dynamics CRM

I will give you three quick scenarios that you will find useful.

  1. Hide the new “friendly” and “informative” notification messages.
  2. Show the full width of a form label without truncation .
  3. Alter the navigation bar text and colour to easily differentiate various environments.

Hiding the notification messages

This is the CSS you’ll use to hide the notification messages. With this you can quick nuke all the messages, across all the environments you specify using the Stylish configuration. This can be done without going to “Settings” and turn each different notification classes (Get Outlook App, Get CRM App, Trial expiry, Mailbox notifications and Install Interactive Service Hub) off. The rule is for any site that ends with dynamics.com.

@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain("dynamics.com") {
  .crmAppMessageBar_green, .crmAppMessageBar {
    display: none !important;
  }
}

Before Stylish

Message Bar Before

After Stylish

Message Bar After

Show the full label width without truncation

Starting from CRM2015, labels are not fully displayed if they are too long. The standard approach is to wrap around, but that is not how it is in CRM. Instead, they are truncated. The workaround solution for this is to increase the section width, but there is a better solution. Using Stylish, you can change the CSS that controls this behaviour. Below is the CSS for this. The rule is for any site that ends with dynamics.com.

@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain("dynamics.com") {
  .ms-crm-InlineEditLabel, .ms-crm-InlineEditLabelText {
    white-space: normal !important;
  }
}

Before Stylish

Label Truncation Before

After Stylish

Label Truncation After

Altering navigation bar text and colour

If you are working across multiple environments, you will like this one. You can change the colour and text using CSS, so that you’ll know where you are, when are constantly switching tabs. I did an extension to do just the colour change, but Stylish does it much better, as it exposes the full power of CSS. You can obviously use themes in CRM2016 to do this. But with this approach:

  1. You can have a theme you like, that is different from the published theme
  2. CRM2015 users can get the same theming goodness

Here is the CSS, you can use for this. The style is just for the navigation bar. You can go crazy and change the form styles, command bar styles and what not.

@-moz-document url-prefix(https://[ENTER THE URL FOR THE RULE]) {
  #navBar {
    background: green !important;
  }
  .navTabLogoText:after {
    content: " DEV" !important;
  }
}

Before Stylish

Navigation Bar Before

After Stylish

Navigation Bar After

Here is some screenshots from Firefox on how to get to the Stylish menu to add custom styles. It is a little bit different in Chrome.

Stylish Menu

I have one rule to do with message bar hiding and preventing label truncation.

Stylish

Here are the links for downloading Stylish

Chrome users head to https://chrome.google.com/webstore/detail/stylish/fjnbnpbmkenffdnngjfgmeleoegfcffe

Firefox users head to https://addons.mozilla.org/en-US/firefox/addon/stylish/

IE users, head to https://www.google.com.au/intl/en/chrome/browser/desktop/index.html Smile

This is a really productive tool to have and is also free.

Bookmarklet: Clone Current Record

Sometimes, you just want to clone a current record, and slightly change the details. e.g. some sort of config entity record. You can use the bookmarklet below, that does cloning only at the parent entity level. Once the new form is open you can change the details and save it. Drag the minified code to your bookmarklet bar, and execute it when you have the CRM record open. The bookmarklet uses query parameters to populate the fields in the new form.

Unminified

(function() {
    var contentPanels = Array.from(document.querySelectorAll('iframe')).filter(function (d) {
        return d.style.visibility !== 'hidden'
      });

    if (!contentPanels || contentPanels.length == 0 || !contentPanels[0].contentWindow.Xrm) {
		alert('CRM Form not detected');
		return;
    }
	var Xrm = contentPanels[0].contentWindow.Xrm;
	var extraq = '';
	var clientUrl = Xrm.Page.context.getClientUrl();
	var entityName = Xrm.Page.data.entity.getEntityName();

	Xrm.Page.data.entity.attributes.forEach(function(c){
		var attributeType = c.getAttributeType();
		var attributeName = c.getName();
		var attributeValue = c.getValue();
		if(!attributeValue ||
		attributeName === 'createdon' ||
		attributeName === 'modifiedon' ||
		attributeName === 'createdby' ||
		attributeName === 'modifiedby') return;

		if(attributeType === 'lookup'){
			extraq += (attributeName+'name='+attributeValue[0].name+'&');
			extraq += (attributeName+'type='+attributeValue[0].entityType+'&');
			attributeValue = attributeValue[0].id;
		}
		if(attributeType === 'datetime'){
			attributeValue = attributeValue.toDateString();
		}
		extraq += (attributeName+'='+attributeValue+'&');
	});

	var newWindowUrl = clientUrl+'/main.aspx?etn='+entityName+'&pagetype=entityrecord'+'&extraqs=?'+encodeURIComponent(extraq)+'etn='+entityName;

	window.open(newWindowUrl, '_blank', "location=no,menubar=no,status=no,toolbar=no", false);
})();

Minified

javascript:(function(){var contentPanels=Array.from(document.querySelectorAll('iframe')).filter(function(d){return d.style.visibility!=='hidden'});if(!contentPanels||contentPanels.length==0||!contentPanels[0].contentWindow.Xrm){alert('CRM Form not detected');return;}
var Xrm=contentPanels[0].contentWindow.Xrm;var extraq='';var clientUrl=Xrm.Page.context.getClientUrl();var entityName=Xrm.Page.data.entity.getEntityName();Xrm.Page.data.entity.attributes.forEach(function(c){var attributeType=c.getAttributeType();var attributeName=c.getName();var attributeValue=c.getValue();if(!attributeValue||attributeName==='createdon'||attributeName==='modifiedon'||attributeName==='createdby'||attributeName==='modifiedby')return;if(attributeType==='lookup'){extraq+=(attributeName+'name='+attributeValue[0].name+'&');extraq+=(attributeName+'type='+attributeValue[0].entityType+'&');attributeValue=attributeValue[0].id;}
if(attributeType==='datetime'){attributeValue=attributeValue.toDateString();}
extraq+=(attributeName+'='+attributeValue+'&');});var newWindowUrl=clientUrl+'/main.aspx?etn='+entityName+'&pagetype=entityrecord'+'&extraqs=?'+encodeURIComponent(extraq)+'etn='+entityName;window.open(newWindowUrl,'_blank',"location=no,menubar=no,status=no,toolbar=no",false);})(); void 0;

I tested this only in CRMOnline on version 8.1.0.359.

End note:

This bookmarklet does not copy across the time component, if you have a datetime field with both date and time on the parent form.

Reference:

https://msdn.microsoft.com/en-us/library/gg334375.aspx#BKMK_ExampleXrmUtilityOpentEntityForm

Level Up: A Chrome extension for CRM Power Users

EDIT (14/11/2016): The extension is now available on Edge as well. Refer https://github.com/rajyraman/Level-up-for-Dynamics-CRM-365-Edge-Browser

Today, I released a new Chrome extension that assists CRM power users. In this initial release there are 12 quick functionalities:

Form helpers

  1. Display Logical names for controls (Original script by Chris Groh http://us.hitachi-solutions.com/blog/2014/10/27/showing-entity-logical-names-on-form/)
  2. God Mode (based on http://www.magnetismsolutions.com/blog/paulnieuwelaar/2014/07/29/activate-god-mode-in-crm-2013—don-t-let-your-users-see-this)
  3. Form properties (Original script by Jared Johnson http://www.magnetismsolutions.com/blog/jaredjohnson/2014/08/03/dynamics-crm-2013-resurrecting-the-form-properties-window-with-bookmarklet)
  4. Dirty fields i.e. fields that have been modified
  5. Display record URL
  6. Display record Id

Navigation helpers

  1. Open record by Id
  2. Open security area
  3. Open System Jobs
  4. Open Solutions
  5. Open Process Definitions
  6. Open main

How do you install it

Download the extension from Chrome store -> Level up for Dynamics CRM

How does it look

Once you install this extension you will see a new rocket icon in the toolbar. If you click the icon, you will get a popup menu.

Screenshot Main

How to use it

You can have a look at this animation below to get an idea about the functionalities.

Functionality Quick Intro

Source code

The source code for the extension can be downloaded from https://github.com/rajyraman/Levelup-for-Dynamics-CRM/

The minimum supported version of Dynamics CRM for this extension is CRM2015.

In the initial version, I have 12 quick actions. Please feel free to fork the repo and add new functionalities that you are currently using with a bookmarklet and finding it useful.

Please also log any issues/feedback in the github repo. I hope this will be a useful extension for improving your productivity.

EDIT (24/06/2016): Fixed credit links for God Mode, Form Properties and Control Logical Names bookmarklets in post and repo.