Error while viewing CRM Service Calendar

A customer was getting the following error when attempting to view the service calendar:-

Record Is Unavailable
The requested record was not found or you do not have sufficient permissions to view it

calendar-issue

The relevant part of the trace file was…

Stack Trace Info: [CrmException: Pointer record exists but referenced record of type 8 not found: d20750f1-db0e-e211-8c48-00155d89dc0f] at Microsoft.Crm.Application.Platform.ServiceCommands.PlatformCommand.XrmExecuteInternal()

Resource type 8 is a systemuser record.

To fix it I had to delete a resource record directly on the SQL server.

DELETE FROM ResourceBase WHERE ResourceId='d20750f1-db0e-e211-8c48-00155d89dc0f'   -- Use GUID from the trace file

The customer had been recently moved to a new server and there were some problems with database corruption so I assume it was related to that.

Advertisements

Unable to delete *ANY* records in Dynamics CRM 2011

We had an odd issue this morning where a client was unable to delete any records on their CRM system whatsoever. When they tried, the generic ‘Unexpected Exception’ box popped-up.

Error[1]

The log-file wasn’t much use…

Generic SQL error.
[Microsoft.Crm.ObjectModel: Microsoft.Crm.Workflow.WorkflowNotificationPlugin]
  [ed1de875-76be-411e-8751-d439fa46287e: Notifies workflow that entity has been deleted.]

After more investigation, I found this in one of the trace files from around the same time…

Exception when executing query:
select "asyncoperation0".AsyncOperationId as "asyncoperationid",
"asyncoperation0".StateCode as "statecode",
"asyncoperation0".StatusCode as "statuscode",
"asyncoperation0".OperationType as "operationtype"
from
AsyncOperation as "asyncoperation0"
where
"asyncoperation0".RegardingObjectId = 'e80c61a3-c036-4238-9516-33aa88508166'
and "asyncoperation0".RegardingObjectTypeCode = 1071
and ("asyncoperation0".StateCode in (2, 0, 1))

Exception: System.Data.SqlClient.SqlException (0x80131904): The provided statistics stream is corrupt.

I was able to reproduce the error reliably by executing the above query in SSMS.

I’ve not really dealt with statistics in SQL before but they appear similar to indexes in configuration. I found I was unable to force an update to the statistics on the asyncoperation table – I recevied the “The provided statistics stream is corrupt” message.

update statistics asyncoperationbase

I then attempted to update each statistic in turn until I found the culprit.

update statistics asyncoperationbase fndx_RegardingObjectId_AsyncOperation

This was one of the two statistics against the RegardingObjectId column. When I attempted to update or delete this statistic specifically, I received an error like this…

Cannot DROP the index  because it is not a statistics collection

Eventually I discovered that there was both an index and a statistic with the same name. I tried dropping the index which was successful and also dropped the statistic of the same name automatically.

To resolve the problem, I used SSMS’s ‘Script as’ functionality to re-create the index. The associated statistic was added automatically.

Screenshot - 08_01_2013 , 11_38_54

“Update statistics” now worked for asyncoperation and the customer was able to delete records again.

CRM 2011 Visual Ribbon Editor – IFD / ADFS Support

I’ve been spending a lot of time with the CRM ribbon lately. This tool seemed really useful (CRM Visual Ribbon Editor).

Unfortunately it wouldn’t work with our third-party IFD / ADFS CRM provider. I grabbed the source and managed to get it working.

I had to make the following changes…

Please note the relevant line numbers in the syntax highlighter

Connection.cs

public bool ADFSMode {get; set;}
XElement adfsElement = new XElement("ADFSMode");
adfsElement.SetValue(ADFSMode);
c.Add(adfsElement);
var adfs = c.Element("ADFSMode");
if(adfs.Value.ToString() == "ADFS") {
  ADFSMode = true;
}
if(this.ADFSMode) {
  ClientCredentials credentials = new ClientCredentials();
  credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
  credentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
  credentials.UserName.UserName = this.Login;
  credentials.UserName.Password = this.Password;
  service = new OrganizationServiceProxy(serviceUrl, null, credentials, null);
} else {
  // This is an existing line...
  service = new OrganizationServiceProxy(serviceUrl, null, creds, null);
}

ConnectionDialog.xaml

DesignHeight="280"
<CheckBox Grid.Row="5" Grid.Column="2" Name="ADFSMode" Margin="4" Content="ADFS / IFD Mode" />

ConnectionDialog.xaml.cs

c.ADFSMode = (bool)this.ADFSMode.IsChecked;

RibbonButton.xaml.cs

if(Connection.CurrentConnection.ADFSMode) {
  string actualURL = Connection.CurrentConnection.ServerUrl;
  if(actualURL.EndsWith("/")) { actualURL = actualURL.Substring(0, actualURL.Length - 1); }
  actualURL += relativeUrl;
  return actualURL;
}

It works and seems publishes changes correctly although I’ve only tried a few changes so far.

Hope this helps someone out.

Firing workflows against multiple records from a ribbon button


UPDATE (2013-04-25):

I recently ran into a problem with this code. When running against large numbers of records (200+) the URL can become very long. This can lead to a number of issues…

1) IIS can hit configured URL length limits causing 500 errors to appear within CRM dialogs. These can be changed in IIS Manager – Request Filtering, Edit Feature Settings.

iis_config3_zoom75

2) I use IE9. Even though the popup blocker was disabled, a dialog would appear saying “A Microsoft Dynamics CRM window was unable to open, and may have been blocked by a pop-up blocker…”. To resolve this, I removed line 21 in my launchWorkflowMulti function. Turns out you don’t have to specify the list of IDs twice (or you no longer have to in current rollups).

Screenshot---25_04_2013-,-18_06_09


I wanted to be able to allow users to “authorise” a set of records from the homepage grid. In this case, authorising a record simply meant executing a particular workflow against it.

The functionality needed to be similar to the standard “Run Workflow” button – with a modal progress bar but without having to select which workflow to run first. Critically the user needed to be able to select any number of records to be authorised in one operation.

Here’s how I got it working…

Configure Ribbon Buttons

Configure your ribbon in the usual way. Or you can give this a try – CRM 2011 Visual Ribbon Editor (http://crmvisualribbonedit.codeplex.com/).

<RibbonDiffXml>
  <CustomActions>
    <CustomAction Id="Mscrm.HomepageGrid.new_casesummary.MainTab.Workflow.Controls.Approve" Location="Mscrm.HomepageGrid.new_casesummary.MainTab.Workflow.Controls._children" Sequence="99">
      <CommandUIDefinition>
        <Button Id="Mscrm.HomepageGrid.new_casesummary.MainTab.Workflow.Controls.Approve.Button" Command="Mscrm.HomepageGrid.new_casesummary.Approve.Command" LabelText="Approve Summary" ToolTipTitle="Approve Case Summary" ToolTipDescription="Run approval procedure for the current Case Summary" TemplateAlias="o1" Image16by16="$webresource:new_icon_thumbsup_16" Image32by32="$webresource:new_icon_thumbsup_32" />
      </CommandUIDefinition>
    </CustomAction>
    <CustomAction Id="Mscrm.HomepageGrid.new_casesummary.MainTab.Workflow.Controls.Authorise" Location="Mscrm.HomepageGrid.new_casesummary.MainTab.Workflow.Controls._children" Sequence="100">
      <CommandUIDefinition>
        <Button Id="Mscrm.HomepageGrid.new_casesummary.MainTab.Workflow.Controls.Authorise.Button" Command="Mscrm.HomepageGrid.new_casesummary.Authorise.Command" LabelText="Authorise Summary" ToolTipTitle="Authorise Case Summary" ToolTipDescription="Run authorisation procedure for the current Case Summary" TemplateAlias="o1" Image16by16="$webresource:new_icon_thumbsup_16" Image32by32="$webresource:new_icon_thumbsup_32" />
      </CommandUIDefinition>
    </CustomAction>
  </CustomActions>
  <CommandDefinitions>
    <CommandDefinition Id="Mscrm.HomepageGrid.new_casesummary.Approve.Command">
      <EnableRules></EnableRules>
      <DisplayRules></DisplayRules>
      <Actions>
        <JavaScriptFunction Library="$webresource:new_script_ribbonHandler" FunctionName="launchWorkflowMulti">
          <StringParameter Value="Are you sure you want to approve the selected records?" />
          <StringParameter Value="E4D7011F-A05F-4676-8630-56D93D89B2C7" />
          <CrmParameter Value="SelectedControlSelectedItemIds" />
          <BoolParameter Value="true" />
          <CrmParameter Value="SelectedEntityTypeCode" />
        </JavaScriptFunction>
      </Actions>
    </CommandDefinition>
  </CommandDefinitions>
</RibbonDiffXml>

So, when clicked, the button fires the function launchWorkflowMulti method within my new_script_ribbonHandler script.

The function’s parameters are:-

  • Confirmation Message – Message to prompt() the user with before continuing
  • GUID – The GUID of the workflow we want to run
  • SelectedControlSelectedItemIds – CRM substitutes this parameter with the list of currently selected records from the grid
  • True/False – This boolean parameter dictates if the screen will be refreshed after the procedure is complete.
  • SelectedEntityTypeCode – CRM substitutes this parameter with the Object Type Code for the current entity

For some reason you have to provide the list of GUIDs both as part of the URL and as an array to the arguments parameter of the built-in openStdDlg() function.

Finally, here’s the function itself:-

function launchWorkflowMulti(confirmMessage, workflowId, recordIds, reload, objectTypeCode) {
  
  // Check variables
  if (objectTypeCode == "") {return;}
  if (recordIds == "") {return;}
  
  // Show confirmation to user
  if (confirmMessage != "") {
    if (!confirm(confirmMessage)) {return;}
  }
  
  // Split recordID list into array (on comma)
  var aRecords = (recordIds + "").split(",");
  if (aRecords.length == 0) {return;}
  
  // Open dialog
  var url = prependOrgName("/_grid/cmds/dlg_runworkflow.aspx")
    + "?iObjType=" + CrmEncodeDecode.CrmUrlEncode(objectTypeCode)
    + "&amp;iTotal=" + CrmEncodeDecode.CrmUrlEncode(aRecords.length)
    + "&amp;wfId=" + CrmEncodeDecode.CrmUrlEncode("{" + workflowId + "}") + ""
    + "&amp;sIds=" + CrmEncodeDecode.CrmUrlEncode(recordIds);
  
  var oresult = openStdDlg(url, aRecords, 500, 200);
  
  if (reload) {window.location.reload(true);}
  
}

CRM Form message / response / error / warning area

I had a requirement to show a warning message to users but didn’t want to use a basic Alert popup. I decided to write a function which inserts a div into the form with various pre-defined options for severity of the message. It uses existing CRM images so no need to create any Web Resources.

Here’s how it looks in the context of a page:

Previews of the colour schemes:

“ok” or “success”


“info” or “information”


“warn” or “warning”


default

Here’s the code:

function ShowWarning(message, mode) {

var boxId = "warning";    // DOM ID of new DIV

if (document.getElementById(boxId)) {
document.getElementById("crmFormTabContainer").removeChild(document.getElementById(boxId));
}

if (!message || message=="") {return;}

var warning = document.createElement("DIV");
warning.style.padding = "10px 10px 10px 50px";
warning.style.border = "1px solid";
warning.style.height = "50px";
warning.style.display = "table-cell";
warning.style.verticalAlign = "middle";
warning.style.margin = "15px 15px 5px 15px";

var colour = "";

switch (mode) {

case "information":
case "info":
warning.style.background = "#BDE5F8 url('/_imgs/error/notif_icn_info.png') 10px 55% no-repeat";
warning.style.borderColor = "#00529B";
colour = "#00529B";
break;

case "success":
case "ok":
warning.style.background = "#DFF2BF url('/_imgs/importwizard_greentick.gif') 20px 55% no-repeat";
warning.style.borderColor = "#4F8A10";
colour = "#4F8A10";
break;

case "warning":
case "warn":
warning.style.background = "#FEEFB3 url('/_imgs/error/notif_icn_warn.png') 10px 55% no-repeat";
warning.style.borderColor = "#9F6000";
colour = "#9F6000";
break;

default:
warning.style.background = "#FFBABA url('/_imgs/error/notif_icn_critical.png') 10px 55% no-repeat";
warning.style.borderColor = "#D8000C";
colour = "#D8000C";
break;

}

warning.innerHTML = "<table cellpadding=0 cellspacing=0 border=0 width=100% height=100%><tr><td valign=middle style=\"font-size: 13px; color: " + colour + ";\">" + message + "</td></tr></table>";

document.getElementById("crmFormTabContainer").insertBefore(warning, document.getElementById("crmFormTabContainer").firstChild);

}

Ended up using a table to ensure the vertical alignment. Any CSS trick I tried didn’t want to play in IE.

To call the function, use something like this:

ShowWarning("You clicked the wrong button!!!", "warn");

Message – The message you want to display
Mode – The severity (“ok”, “warn”, “info”) you want to use (optional). Any non-recognised mode will be treated as an error (red).

Plugin “Associate” example

Couldn’t find any existing examples of using the Associate message in plugins so, having figured it out myself, I thought I’d share.

The first slightly weird thing is that you can’t register an Associate message against a particular entity. Any Associate steps will be executed every time any entity is associated with another.

So, in our code, the first thing we need to do is check we’re running against the correct relationship.

if (!context.InputParameters.Contains("<strong>Relationship</strong>")) { return; }
Relationship relationship = (Relationship)context.InputParameters["Relationship"];
if (relationship.SchemaName != "hpd_hpd_invoiceperiod_incident") { return; }

On line 3 I specify the name of the relationship I want this plugin to work for. This can be found in the Relationships area of Customizations. In my case, it’s for a relationship between Cases (incident) and a custom entity called Invoice Period (hpd_invoiceperiod).

The other 2 useful parameters we can get from InputParameters are Target and RelatedEntities.

if (!context.InputParameters.Contains("<strong>Target</strong>")) { return; }
EntityReference target = (EntityReference)context.InputParameters["Target"];
if (!context.InputParameters.Contains("<strong>RelatedEntities</strong>")) { return; }
EntityReferenceCollection related = (EntityReferenceCollection)context.InputParameters["RelatedEntities"];

Relationship is a reference to the relationship which this association is working against
Target is a reference to the primary Entity – i.e. the entity of the open CRM form
RelatedEntities is a set of references pointing to the records to associate with – i.e. the records the user just selected

N.B. In my testing RelatedEntities never seemed to contain more than one reference – the plugin just gets called once per association. I coded my plugin to handle both sitations just in case.

So once these values are available, we can add whatever logic is required to allow / deny the association. In my case, I used a fetchXML query to check that the selected records havent already been associated to a record. You can perform fetchXML queries against the join tables in CRM (in my case hpd_hpd_invoiceperiod_incident) which normally aren’t available.

To deny the operation, just throw a InvalidPluginExecutionException with a relevant error message.

Activity Feeds opportunity create error fix

Just a quick one. Someone installed a managed solution from Microsoft called “Activity Feeds”. No idea what it does but it stopped the users creating any new opportunities.

It just gave the generic “An error has occurred” message but downloading the log revealed this…

Need to start a transaction before commit

It seems to be a configuration issue where the Create operation of the OpportunityCreate element of the plugin is double-registered. Disabling the first of these steps seems to fix it.

Default email templates / signatures in CRM 4

I added some code to a customer’s system to set a default email template (signature). This code is avaiable from various places through Google. However, there were some strange inconsitencies when we added it to their live system – in some cases the signature would load, sometimes it wouldn’t.

Eventually I figured out that sometimes Microsoft’s own onLoad code was removing the signature I had set. Basically it was doing a query to find any email body (because the same form / code is used when looking at email history) and when it found none it was overwriting the email body (along with my signature).

In-fact, it was setting the body to something like…

I added a StripHTML function and a 500msec delay to my code (plus a bunch of additional null checks) which seems to have solved the problem.

Here’s the code for the onLoad of the email form. If you want to use it, don’t forget to change the GUID on line 7 for your specific template.

function sig() {

  if (crmForm.FormType != 1) {return;}

  // Pre-fill a template signature
  var drawSignature = false;
  var emailTemplateToLoad = "D802E626-4DB1-E011-81DD-0015C5FF4D30";   // sandbox "BAA8272F-F7AD-E011-93CB-0015C5FF4CDB"

  // Check if description is blank or similar
  var theDescription = crmForm.all.description.DataValue;
  if (theDescription == null) {
    drawSignature = true;
  } else if (theDescription == undefined) {
    drawSignature = true;
  } else if (stripHTML(theDescription) == "") {
    drawSignature = true;
  } else if (stripHTML(theDescription).length < 10) {
    drawSignature = true;
  }

  if (drawSignature) {

    // Get Regarding object details
    if (!crmForm.all.regardingobjectid) {return;}
    var RegardingItems = crmForm.all.regardingobjectid.DataValue;

    if (RegardingItems) {

      var regardingObjectId = RegardingItems[0].id;
      var regardingObjectType = RegardingItems[0].type;

      if (regardingObjectId == null || regardingObjectId == "") {return;}
      if (regardingObjectType == null || regardingObjectType == "") {return;}

      var command = new RemoteCommand("EmailTemplateService", "GetInstantiatedEmailTemplate");
      command.SetParameter("templateId", emailTemplateToLoad );
      command.SetParameter("objectId", regardingObjectId);
      command.SetParameter("objectTypeCode", regardingObjectType);
      var result = command.Execute();

      if (result.Success) {
        var o = new Object();
        o.EmailBody = "";
        o.EmailSubject = "";
        if(typeof(result.ReturnValue) == "string") {
          oXml = CreateXmlDocument(false);
          oXml.loadXML(result.ReturnValue);
          o.EmailBody = oXml.selectSingleNode("template/body").text;
          o.EmailSubject = oXml.selectSingleNode("template/subject").text;
          crmForm.all.description.InsertValue(o.EmailBody);
        }
      }

    }

  } // drawSignature == true

}

function stripHTML(xx) {
  var tmp = document.createElement("DIV");
  tmp.innerHTML = xx;
  return tmp.textContent||tmp.innerText;
}

var a = window.setTimeout(sig, 500);

Preview entity data in an iFrame (CRM 2011)

We wanted to be able to display a bunch of useful information when a user selected a record from a lookup. I wanted to use as much native CRM stuff as possible so non-developers in the team could change what fields are displayed and where without having to modify any code. I think it turned out pretty cool. Hope some people find it useful.

The result

In the screenshot, the relevant bit is under the “Op Site Data” heading. When a user selects an “Op Site”, the tab pops open and the grey box is displayed showing related data for that record.

So how is it done ?

IFrame

Add a new tab and iFrame to the relevant form. Set the URL of the iFrame to “about:blank”.
Make sure you un-tick “Restrict cross-frame scripting”.

Attach a JavaScript function to the onReadyStateChange event of the iFrame. In this case, I’ve called it iframe_onload.

Related Entity Form

You need to add a new form to the related entity. In this case it’s a custom entity called Op Site. It doesn’t matter what name you give it – I called it “Preview”.

Set the new form up to only display the fields you want. I found it best to use a three column layout.

Print Preview

Now here’s the science bit. Go and look at the new form you created and do a Print Preview. This will bring up a read-only version of the form – this is the basis of what we’ll display in the iFrame. Copy the URL of the preview to the clipboard.

We’ll have to get rid of those Print and Close toolbar buttons and the space-wasting headings and whitespace.

New form in print preview mode

JavaScript

NB This stuff requires jQuery. I was using v1.6.2.

There are two functions to perform this preview. The first handles the opening / closing of the tab and sets the URL for the iFrame. The second cleans up the content of the iFrame so it takes up less room.

First Function

I called this function ShowOpSiteData. It needs to be attached to the Form onLoad event as well as the Field’s onChange event (in this case the field is called Op Site). You will need the URL of the print preview page that you copied earlier.

function ShowOpSiteData() {

  // Collapse the preview tab
  var elTab = Xrm.Page.ui.tabs.get("tab_4");
  elTab.setDisplayState("collapsed");

  // Get the value (GUID) of the selected lookup
  var opSite = Xrm.Page.getAttribute("hpd_opsiteid").getValue();
  if (!opSite) { return; }
  var opSiteId = opSite[0].id;
  if (opSiteId == "") {return;}

  // Set the iFrame URL to the print preview URL.
  // Insert the GUID into the URL.
  if (opSiteId != "") {
    var el = document.getElementById("IFRAME_opsitedata");
    var el2 = document.getElementById("printArea");
    var url = "/_forms/print/print.aspx?allsubgridspages=false&amp;formid=69144f31-713b-4eeb-9d89-096d893be6a2&amp;id=" + escape(opSiteId) + "&amp;objectType=10004";
    el.src = url;
  }

}

Second Function

I called this function iframe_onLoad (originally I had it attached (via code) to the iFrame’s onLoad event). The function needs to be attached to the iFrame’s onReadyStateChange event. This function is where the jQuery comes in – mainly for the Sizzle selector system.

The function basically goes into the iFrame and removes the toolbar, the headings and the big whitespaces. See the comments for full details.

function iframe_onLoad() {

  // Grab the iFrame element.
  // Check it's fully loaded and we have the page is correct.
  var iFrame = document.getElementById("IFRAME_opsitedata");
  if (iFrame.readyState != "complete") {return;}
  if (iFrame.src.indexOf("/_forms/print/print.aspx") == -1) {return;}

  // Grab the preview iFrame as a jQuery object. Remove the toolbar.
  var IF1 = jQuery("#IFRAME_opsitedata");
  IF1.contents().find("tr#printHeader").remove();

  // Grab the sub-iFrame within the preview iFrame.
  var IF2 = IF1.contents().find("iframe#printMain");
  // Remove the big header and icon
  IF2.contents().find("tr.ms-crm-Form-HeaderContainer").remove();
  // Remove the big whitespace at the top
  IF2.contents().find("div.ms-crm-Tab-Print").first().remove();
  // Remove a bunch of whitespace around the second iFrame
  IF2.contents().find("td.ms-crm-Form-Page-Main-cell").css("padding","0px");
  // Reinstate the whitespace next to the scrollbar
  IF2.contents().find("td.ms-crm-Form-Page-Main-cell").css("padding-right","8px");
  // Remove the big whitespace at the bottom
  IF2.contents().find("td.ms-crm-Form-Footer").parent().remove();

  // Remove the header text. The text isn't within any specific tag so just performing a replace.
  // The only part of the code that isn't directly re-usable
  IF2.contents().find("div.ms-crm-Tab-Print").each(function() {
    jQuery(this).html( jQuery(this).html().replace("General","") );
    jQuery(this).html( jQuery(this).html().replace("Response Information","") );
    jQuery(this).html( jQuery(this).html().replace("Notes","") );
  });

  // Expand Op Site Data tab, revealing the fully rendered preview
  var elTab = Xrm.Page.ui.tabs.get("tab_4");
  elTab.setDisplayState("expanded");

}

Success

That’s it! Feel free to leave a comment if you found this useful, spot a problem or have a question.

Removing a ribbon button in CRM 2011

I needed to remove the Save and Close button from the Case entity when the form is in Create mode.

1) Find the Id of the button we are interested in.

Look at the output from the “ribbon” project of the CRM 2011 SDK. Within incidentribbon.xml we find:-

<Button
  Id="Mscrm.Form.incident.SaveAndClose"
  ToolTipTitle="$Resources:Mscrm_Form_Other_MainTab_Save_SaveAndClose_ToolTipTitle"
  ToolTipDescription="$Resources(EntityDisplayName):Ribbon.Tooltip.SaveAndClose"
  Command="Mscrm.SaveAndClosePrimary"
  Sequence="30"
  LabelText="$Resources:Ribbon.Form.MainTab.Save.SaveAndClose"
  Alt="$Resources:Ribbon.Form.MainTab.Save.SaveAndClose"
  Image16by16="/_imgs/FormEditorRibbon/SaveAndClose_16.png"
  Image32by32="/_imgs/ribbon/SaveAndClose_32.png"
  TemplateAlias="o1" />

We are interested in the Command value (Mscrm.SaveAndClosePrimary).

2) Export the entity as part of a blank solution. Open the corresponding customizations.xml and find the RibbonDiffXml section.

Expand the CommandDefinitions section and create new entries within it, using the Command value as follows:-

Before:

<CommandDefinitions />

After:

<CommandDefinitions>
<CommandDefinition Id="<strong>Mscrm.SaveAndClosePrimary</strong>">
<EnableRules>
<EnableRule Id="<strong>Mscrm.SaveAndClosePrimary</strong>.EnableRule" />
</EnableRules>
<DisplayRules>
<DisplayRule Id="<strong>Mscrm.SaveAndClosePrimary</strong>.DisplayRule" />
</DisplayRules>
    <Actions>
      <JavaScriptFunction FunctionName="Mscrm.RibbonActions.saveAndCloseForm" Library="/_static/_common/scripts/RibbonActions.js">
        <CrmParameter Value="PrimaryControl" />
      </JavaScriptFunction>
    </Actions>
</CommandDefinition>
</CommandDefinitions>

Copy the Actions section direct from the original file.

3) We now have two new rules (.EnableRule and .DisplayRule) which need to be defined within RuleDefinitions.

Complete the RuleDefinitions section as follows:-

Before:-

<RuleDefinitions>
<TabDisplayRules />
<DisplayRules />
<EnableRules />
</RuleDefinitions>

After:-

<RuleDefinitions>
<TabDisplayRules />
<DisplayRules>
<DisplayRule Id="Mscrm.SaveAndClosePrimary.DisplayRule">
<FormStateRule Default="0" State="Create" InvertResult="true" />
</DisplayRule>
</DisplayRules>
<EnableRules>
<EnableRule Id="Mscrm.SaveAndClosePrimary.EnableRule">
<FormStateRule Default="0" State="Create" InvertResult="true" />
</EnableRule>
</EnableRules>
</RuleDefinitions>

Microsoft have a good list of the various rules that can be implemented within this section. The two shown above will show and enable the button if the form is not in Create mode.

4) Zip, Import and Publish the entity solution and voila!