Monday, September 29, 2014

Bulk Delete From Multiple Entities With 1 Button

Over the years I've had the need to delete all records from multiple entities, such as when testing out data migrations or working with the new Unified Service Desktop.  You can use advanced find to find/delete the records or go into the bulk deletion module but then you have to do each entity one at a time and it can quickly become a hassle.

I created a very simple JavaScript web resource to fix this issue which has saved me a lot of time over the last few months as there are a lot of entities that USD works with.  All you need to do is create a web resource that makes a bulk-delete call and call that function with a button in the ribbon (for CRM 2011 that is).

Here's what the button looks like and the popup that's shown, plus the resulting bulk deletion job that's created:




Here are screenshots of the Ribbon Editor tool with the pieces needed to add this customization.  The screenshots below show the setup of this button used to delete data.

First the position of the button and the text:


Here are the button properties:



The command pointing to a JavaScript web resource:




And the rules indicating that the button should only be on the customization and solution pages:

 



Here is the JavaScript called by the button.  You can update the JobName and alert message at the end to be whatever you'd like.

-------------

function getServerUrl()
{
    try
    {
        var ServicePath = "/XRMServices/2011/Organization.svc/web";
        var serverUrl = "";
        if (typeof GetGlobalContext == "function")
        {
            var context = GetGlobalContext();
            serverUrl = context.getClientUrl();
        }
        else
        {
            if (typeof Xrm.Page.context == "object")
            {
                serverUrl = Xrm.Page.context.getClientUrl();
            }
            else
            {
                throw new Error("Unable to access the server URL");
            }
        }
        if (serverUrl.match(/\/$/))
        {
            serverUrl = serverUrl.substring(0, serverUrl.length - 1);
        }
        return serverUrl + ServicePath;
    }
    catch (e)
    {
    }
}

function RemoveData()
{
    try
    {
        var requestMain = ""
        requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
        requestMain += "  <s:Body>";
        requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
        requestMain += "      <request i:type=\"b:BulkDeleteRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
        requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
        requestMain += "          <a:KeyValuePairOfstringanyType>";
        requestMain += "            <c:key>QuerySet</c:key>";
        requestMain += "            <c:value i:type=\"a:ArrayOfQueryExpression\">";
        requestMain += "              <a:QueryExpression>";
        requestMain += "                <a:EntityName>[Entity1]</a:EntityName>";
        requestMain += "              </a:QueryExpression>";
        requestMain += "              <a:QueryExpression>";
        requestMain += "                <a:EntityName>[Entity2]</a:EntityName>";
        requestMain += "              </a:QueryExpression>";
...
...
        requestMain += "            </c:value>";
        requestMain += "          </a:KeyValuePairOfstringanyType>";
        requestMain += "          <a:KeyValuePairOfstringanyType>";
        requestMain += "            <c:key>JobName</c:key>";
        requestMain += "            <c:value i:type=\"d:string\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">Data Bulk Delete</c:value>";
        requestMain += "          </a:KeyValuePairOfstringanyType>";
        requestMain += "          <a:KeyValuePairOfstringanyType>";
        requestMain += "            <c:key>SendEmailNotification</c:key>";
        requestMain += "            <c:value i:type=\"d:boolean\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">false</c:value>";
        requestMain += "          </a:KeyValuePairOfstringanyType>";
        requestMain += "          <a:KeyValuePairOfstringanyType>";
        requestMain += "            <c:key>ToRecipients</c:key>";
        requestMain += "            <c:value i:type=\"d:ArrayOfguid\" xmlns:d=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\" />";
        requestMain += "          </a:KeyValuePairOfstringanyType>";
        requestMain += "          <a:KeyValuePairOfstringanyType>";
        requestMain += "            <c:key>CCRecipients</c:key>";
        requestMain += "            <c:value i:type=\"d:ArrayOfguid\" xmlns:d=\"http://schemas.microsoft.com/2003/10/Serialization/Arrays\" />";
        requestMain += "          </a:KeyValuePairOfstringanyType>";
        requestMain += "          <a:KeyValuePairOfstringanyType>";
        requestMain += "            <c:key>RecurrencePattern</c:key>";
        requestMain += "            <c:value i:type=\"d:string\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\" />";
        requestMain += "          </a:KeyValuePairOfstringanyType>";
        requestMain += "          <a:KeyValuePairOfstringanyType>";
        requestMain += "            <c:key>StartDateTime</c:key>";
        requestMain += "            <c:value i:type=\"d:dateTime\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">0001-01-01T00:00:00</c:value>";
        requestMain += "          </a:KeyValuePairOfstringanyType>";
        requestMain += "        </a:Parameters>";
        requestMain += "        <a:RequestId i:nil=\"true\" />";
        requestMain += "        <a:RequestName>BulkDelete</a:RequestName>";
        requestMain += "      </request>";
        requestMain += "    </Execute>";
        requestMain += "  </s:Body>";
        requestMain += "</s:Envelope>";
        var req = new XMLHttpRequest();
        req.open("POST", getServerUrl(), true)
        req.setRequestHeader("Accept", "application/xml, text/xml, */*");
        req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
        req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
        req.send(requestMain);
        alert("Delete command received.  Data has been queued to be deleted and should start processing shortly.");
    }
    catch (e)
    {
    }
}

----------

And finally, if you can't use the ribbon editor tool and have to rely on old-fashioned XML editing, see below:

Note: [Annotations are in bold] and should be removed/replaced in an actual implementation.
Note:  The org that I’m importing to uses a prefix of ‘hac’ so you see it many times below.


<?xml version="1.0" encoding="utf-8"?>
<RibbonDiffXml>
  <CustomActions>
    <CustomAction Id="mcs.hac.ApplicationRibbon.RemoveCCDData.Button.CustomAction" Location="Mscrm.BasicHomeTab.Tools.Controls._children" Sequence="6">
      <CommandUIDefinition>
        <Button Alt="$LocLabels:hac.ApplicationRibbon.RemoveCCDData.Button.Alt" Command="hac.ApplicationRibbon.RemoveCCDData.Command" Description="Main Text of the Button" Id="hac.ApplicationRibbon.RemoveCCDData.Button" Image32by32="$webresource:hac_/shared/images/recycleBin.png" [This is a custom image I added as a web resource] LabelText="$LocLabels:hac.ApplicationRibbon.RemoveCCDData.Button.LabelText" Sequence="6" TemplateAlias="o1" ToolTipTitle="$LocLabels:hac.ApplicationRibbon.RemoveCCDData.Button.ToolTipTitle" ToolTipDescription="$LocLabels:hac.ApplicationRibbon.RemoveCCDData.Button.ToolTipDescription" />
      </CommandUIDefinition>
    </CustomAction>
  </CustomActions>
  <Templates>
    <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
  </Templates>
  <CommandDefinitions>
    <CommandDefinition Id="hac.ApplicationRibbon.RemoveCCDData.Command">
      <EnableRules />
      <DisplayRules>
        <DisplayRule Id="hac.ApplicationRibbon.OnlyInSolutionArea.DisplayRule" /> [This is where I’m setting the button to be shown only when looking at solutions, see the DisplayRule nodes below]
      </DisplayRules>
      <Actions>
        <JavaScriptFunction FunctionName="RemoveCCDData" Library="$webresource:hac_/shared/RemoveCCDData.js" /> [This is the web resource with the code to execute]
      </Actions>
    </CommandDefinition>
  </CommandDefinitions>
  <RuleDefinitions>
    <TabDisplayRules />
    <DisplayRules> [If you want to have this button shown on other pages, indicate that here]
      <DisplayRule Id="hac.ApplicationRibbon.OnlyInSolutionArea.DisplayRule">
        <OrRule>
          <Or>
            <PageRule Address="/tools/Solution/home_solution.aspx" />
          </Or>
          <Or>
            <PageRule Address="/tools/systemcustomization/systemcustomization.aspx" />
          </Or>
        </OrRule>
      </DisplayRule>
    </DisplayRules>
    <EnableRules />
  </RuleDefinitions>
  <LocLabels>
    <LocLabel Id="hac.ApplicationRibbon.RemoveCCDData.Button.Alt">
      <Titles>
        <Title description="Bolded Title of the Button" languagecode="1033" />
      </Titles>
    </LocLabel>
    <LocLabel Id="hac.ApplicationRibbon.RemoveCCDData.Button.LabelText">
      <Titles>
        <Title description="Sub Text of the Button" languagecode="1033" />
      </Titles>
    </LocLabel>
    <LocLabel Id="hac.ApplicationRibbon.RemoveCCDData.Button.ToolTipDescription">
      <Titles>
        <Title description="Tooltip Description of the Button" languagecode="1033" />
      </Titles>
    </LocLabel>
    <LocLabel Id="hac.ApplicationRibbon.RemoveCCDData.Button.ToolTipTitle">
      <Titles>
        <Title description="Tooltip Title of the Button" languagecode="1033" />
      </Titles>
    </LocLabel>
  </LocLabels>

</RibbonDiffXml>

-------------

Hope this helps save you some time!