Entitlements…some of the limitations we faced

 

CRM has entitlements since CRM 2013 and it was major functionality addition for Service Management. For our Service Module implementation we decided to implement Entitlement, however sooner we found that our functional requirement does not fit into OOB Entitlement functionality and finally we have to opted for Custom Solution.

Let me explain what we intended to do

  1. In CRM we had Customer Hierarchy enabled in a such way that there would be Parent Customer at root and it will  have children Customers from various regions. Logically its one customer only however we have to maintained the hierarchy because products can be sold to any of the Customer in Hierarchy.
  2. Since we treat all the Customer is entire Hierarchy as “One” logical customer, there are no separate entitlements for each of these Customers, all the Customers in a hierarchy follow the same entitlements and SLAs
  3. On Cases for Contacts there are 2 fields provided, one is responsiblecontactid and other one is primarycontactid. As you might be already knowing, primarycontactid is tightly coupled with customerid field in sense that when Customer is selected as Account only Account-Contacts can be assigned to primarycontactid field on Case. On the other hand responsiblecontactid  is stand alone field having no dependency on any other field. We for our Case form selected primarycontactid to capture the Contact on Customer.

 

Now here is what we faced as limitations.

  1. We had defined Entitlements only for Root level  (Parent) Customer in CRM, hence there was just one Entitlement per Customer Hierarchy.
  2. There were no Contacts added to Entitlements.
  3. There were no Products added to Entitlements.
  4. Cases can be created for any Customer within Hierarchy and Contact was mandatory field on Case form. Hence every case would have Customer (Account) and Contact.
  5. Now in such cases System would not match Entitlement since it would never match the Customer and Contacts on Case with Entitlements.
  6. Even though User tries to select the Entitlement on form it will throw error as shown below

clip_image001

One of the solution to overcome these limitations was to create “same” entitlements for each of the Customers in Hierarchy which we this would be maintenance overhead if we have to alter Entitlements for Customer.

So basically we could not leverage OOB Entitlements for our Business Process.

I think there should be some enhancement made to Entitlements functionality to honor Hierarchy of Customer so that we minimize creating Entitlements for all the Customer in hierarchy.

CRM 2011 & CRM 2013–Usage Audit Report (On-Premise Deployment)

For On-Premise deployment of CRM 2011 or CRM 2013, I have written a SQL Query which will give details about who when accessed the CRM and what was his/her role in CRM
CRM does have a Audit Summary View which let you see this details but you cannot export this data from CRM Audit Summary entity neither can you have carts/graphs around that data in CRM Report.
Here is script, run it as db_owner on CRM Database. You can also write SQL Connection in Excel and pull this data dynamically.

SELECT Usage.*

,(SELECT SecRole.Name +‘,’FROM SystemUserRoles SysUser

INNERJOINRole SecRole ON SysUser.RoleId = SecRole.RoleId

INNERJOIN SystemUse SU ON SysUser.SystemUserId = SU.SystemUserId

WHERE SU.DomainName = Usage.UserName

FORXMLPATH ())AS SecRoleNames

FROM

(

SELECT U.fullname AS [FullName], U.DomainName AS [UserName]

,DATEPART(YYYY,DATEADD(HH,(DATEDIFF(HH,GetUTCDate(),GetDate())), A.CreatedOn))AS [Year]

,DATEPART(MM,DATEADD(HH,(DATEDIFF(HH,GetUTCDate(),GetDate())), A.CreatedOn))AS [Month]

,DATEPART(DD,DATEADD(HH,(DATEDIFF(HH,GetUTCDate(),GetDate())), A.CreatedOn))AS [Day]

,COUNT(*) [Counter]

,MIN(DATEADD(HH,(DATEDIFF(HH,GetUTCDate(),GetDate())), A.CreatedOn)) [FirstAccessAt]

,MAX(DATEADD(HH,(DATEDIFF(HH,GetUTCDate(),GetDate())), A.CreatedOn)) [LastAccessAt]

FROM AUDIT A

INNERJOIN SystemUser U ON A.objectid = U.Systemuserid

WHEREAction= 64

GROUPBY

U.fullname, U.DomainName

,DATEPART(YYYY,DATEADD(HH,(DATEDIFF(HH,GetUTCDate(),GetDate())), A.CreatedOn))

,DATEPART(MM,DATEADD(HH,(DATEDIFF(HH,GetUTCDate(),GetDate())), A.CreatedOn))

,DATEPART(DD,DATEADD(HH,(DATEDIFF(HH,GetUTCDate(),GetDate())), A.CreatedOn))

) Usage

Hope you find this script handy.

CRM 2011–Generic Rollup Solution

Project Description

Many time we need to do the Rollup of certain child attribute data into parent attributes. For example number of Contacts for an Account or total order for order lines etc.

The best way to achieve this rollup is using CRM Plugins, however for each Rollup on each entity we have to write separate plugin which will take care of local attributes and type of aggregation required.

This Generic solution is meant to address this problem. It has generic code which works with configuration stored in CRM and can be applied to any entities.

This Generic Solution servers following purpose

1. It rollup data

2. Aggregation is done terms of Count, Average and Sum

3. A single Plugin Code is used for all the Entities.

4. Aggregation is completely configurable without any code change.

5. Aggregation changes in based on metadata.

How to configure Generic Rollup?

  1. Import the Managed Solution provided with this Project.
  2. If Settings does not reflect the entity link for “Rollup Configuration” entity, modify the customization to add the link to Settings (or any other Group).
  3. Create new record for “Rollup Configuration” entity
    1. At first enter the Child Entity Name and the type of aggregation and Save the Record (Do not close the Form)
    2. Once the record is saved under Rollup Config tab, enter the Configuration as follows
      1. Child Entity Entity Logical Name: this field will be auto populated based on what you have entered on main form.
      2. Child Entity Aggregate Attribute Name: This is the attribute from Child entity on which aggregation needs to be done. Note if aggregation type in Count then select the last items from this list which is PrimaryKey attribute name from Child Entity.
      3. Parent Entity Name (Lookup Attribute): This Dropdown contains all the Entities which has One-To-Many relationship with Child entity. Select Entity to which you want to update the aggregation results.
      4. Parent Entity Aggregation Attribute Name: This is attribute from Parent entity to update aggregation results.
    3. Hit Save once selection is completed and you are done !!!

image

 

What happens behind the scene?

Once you import the solution it also imports 3 plugin SDK Messages Processing steps, namely for Create, Update and Delete. These Plugin messages are configured to triggered on All the entities in CRM. Whenever the record is Created, Updated or Deleted this code checks whether rollup is configured for the entity. If yes it reads the configuration and calculates the aggregate values and updates it in Parent entity.

 

By default plugin will be triggered on all the entities irrespective of whether rollup is configured to entity, this can cause a performance problem. So in order to avoid Plugin triggering each entity, you can configured this messages separately for only those entities which need Rollup. This is extra admin steps that needs to be done, but it can give better performance.

Please try out the solution and let me know your feedback.

 

CRM Managed Solution is available on CodePlex

CRM 2011–Utilities Developed by me

It started as small experiment to create a CRM 2011 add-on to address my project requirement. For my project had this requirement which needed to develop a utility which I developed using Silverlight, later on I realized that requirement that it was catering is kind of universal requirement and many other projects will need it, so I created it as Managed Solution and uploaded on CodePlex so that others can use it. That was my first ever CodePlex project for CRM 2011 ISV. Later on I kept on developing CRM 2011 managed solutions and made it available on CodePlex. Now when I look back ever since I started it in 04/2011 so far I have developed 10 Managed Solutions !!!

I received good response in terms of Page Visits and Downloads and probably that’s what kept me encouraging to develop new CRM 2011 Managed Solutions.

Special thanks to Tanguy for making available the wonderful CRM Connection Control, Free !!!

Following are Utilities / Add-On that I have developed so far and which are available on CodePlex for free download

# Name Description
1 CRM 2011 Silverlight Web Resource for User Settings Silverlight based Web Resource to managed User Settings
2 CRM 2011 – WinForm based User Settings Console Win Form based utility to managed User Settings. It also has added feature to check whether existing User is valid AD User.
3 CRM 2011 TreeView for Lookup Treeview form of Lookup data which has Parent Child relationship. For example Account – Parent Account
4 CRM 2011 TreeView for Dependent Picklist Treeview form for Dependent Picklist.
5 CRM 2011 Lookup Preview HTML Lookup preview on CRM Form where Lookup control is placed.
6 CRM 2011 Un-managed Solution Bulk Export Win Form based utility to do Bulk Export of CRM 2011 Un-managed solutions.
7 CRM 2011 AttributeMapping CRM 2011 Attribute Mapping for updating Lookup mapping field data.
8 CRM 2011 Bulk Disassociation of 1:N relationship records This Custom Ribbon allows User to do bulk disassociation of 1:N relationship records, which other wise has to do one by one. For example if Account has multiple Contacts and User wanted to disassociate them they can do it using this Add-On
9 CRM 2011 – Many-To-Many Relationship Entity View This N:N view facilitates to see records in single view.
10 CRM 2011 Global Quick Search Global Quick search allows User to carry our CRM’s OOB Quick Find on multiple entities and shows results in a single page.

CRM 2011: Many-to-Many (N:N) Relationship Entity View

Microsoft Dynamics CRM has many to many relationship which enables user to establish N:N relationship between 2 CRM entities. CRM creates intersect entity for each of this relationship.

Once established user can associate entities from each other and view it from each other’s form using Left Navigation link or Sub-Grid placed in form. However there is no Advance find available on these intersect entities and only way to see it from CRM Form. Association and dissociation can only be done from CRM form. Although this serves most of the purpose, there can be scenario where user wanted to see all the data that is available of intersect entity, for example Security Role and System User where we might wanted to see all the User in given security Roles.

 

In order over come this limitation I have created Silverlight Web Resource which will allow user to view all many-to-many related records from single page.

It has following components

1. Plug-in to generate XML (Data) Web Resource which gets saved in CRM Web Resource store. This plug-in is wired up with “Publish All” messages hence gets fired every time “Publish All” action is called.

2. Silverlight Web Resource, to enable selection of Intersect entity and do paging.

 

Once managed solution is imported, CRM Customizer needs to edit SiteMap and add link to mtm_/ManyToManyBulkEditTestPage.html at appropriate Group/Sub-Group

 

Managed Solution is available at https://crm2011manytomanyrel.codeplex.com/releases/view/94756

 

Screenshots

  • Following is how Silverlight page looks like

image

  • There are 3 options for relationship type selection

image

  • Once relationship type is selected from combo box, appropriate View names are populated in View list

image

  • Once the View is selected data is rendered from give view

image

 

image

CRM 2011 – Bulk Removal of Child Entity records from Parent Entity Form.

In CRM 2011, in a 1:N (One-to-Many) entity relationship one entity has a lookup attribute that allows an entity object to store a reference to another entity object for a particular entity.

For 1:N entity relationship Parent Entity will have Left Navigation link to see all the Children Entity records associated with it. User can add new child records from Parent form itself.

However if User have to remove the children records from Parent Entity, they have to open each child record form and set the Lookup up value as null or blank. This is some times lengthy processes especially if the number of records are high.

To address this problem I created CRM solution which will allow User to remove child records from Parent Entity form and they need not go to individual child record form.

CRM Solutions are available on CodePlex

This solution adds a Custom Ribbon button to the “Records” Group of Sub-Grid Ribbon (List Tools). 

 

Screenshot 1: “Remove” button is added to Sub-Grid Ribbon.

image

 

Screenshot 2: “Remove” button is enabled only when there is at least one selected record in Sub-Grid

image

 

Custom “Remove” Ribbon button is displayed only when following conditions are satisfied

1. Entity relationship is 1:n, Remove button is not enabled for n:n (many – to –many ) relationships

2. User has “Basic” privilege on “Write” access on Selected Sub-Grid entity.

3. User has “Basic” privilege on “Append” access on Selected Sub-Grid entity.

4. User has “Basic” privilege on “AppendTo” access on Primary Entity in Form.

 

Screenshot 3: Confirm Removal dialog box is shown which includes count of selected records by User in Sub-Grid

image

 

Screenshot 4: Process in Progress Message

image

 

Screenshot 5: Error message

image

 

If all records are successfully removed, then Conform dialog box will automatically close and refresh sub-grid.

 

For those who wanted to alter Ribbon customizations to make it available only for selected entities can alter Application Ribbon XML.

New Display Rules can be added.

Following is Ribbon Customization done in Application Ribbon

<RibbonDiffXml>

    <CustomActions>

      <CustomAction Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.CustomAction" Location="Mscrm.SubGrid.{!EntityLogicalName}.MainTab.Management.Controls._children" Sequence="41">

        <CommandUIDefinition>

          <Button Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren" Command="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command" Sequence="130" ToolTipTitle="$LocLabels:makeer.SubGrid.EntityLogicalName.MainTab.Management.RemoveChildren.LabelText" LabelText="$LocLabels:makeer.SubGrid.EntityLogicalName.MainTab.Management.RemoveChildren.LabelText" ToolTipDescription="$LocLabels:makeer.SubGrid.EntityLogicalName.MainTab.Management.RemoveChildren.Description" TemplateAlias="isv" Image16by16="$webresource:makeer_/IMG/PNG/Remove_16x16.png" Image32by32="$webresource:makeer_/IMG/PNG/Remove_32x32.png" />

        </CommandUIDefinition>

      </CustomAction>

    </CustomActions>

    <Templates>

      <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>

    </Templates>

    <CommandDefinitions>

      <CommandDefinition Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command">

        <EnableRules>

          <EnableRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.EnableRule.SelectionCountRule" />

        </EnableRules>

        <DisplayRules>

          <DisplayRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.DisplayRule.RelationshipTypeRule" />

          <DisplayRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.DisplayRule.EntityPrivilegeRule" />

          <DisplayRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.DisplayRule.EntityPrivilegeRule2" />

          <DisplayRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.DisplayRule.EntityPrivilegeRule3" />

        </DisplayRules>

        <Actions>

          <JavaScriptFunction FunctionName="RemoveChildren" Library="$webresource:makeer_/Scripts/RemoveChildren.js">

            <CrmParameter Value="PrimaryEntityTypeName" />

            <CrmParameter Value="SelectedEntityTypeName" />

            <CrmParameter Value="SelectedControlSelectedItemIds" />

            <CrmParameter Value="SelectedControl" />

          </JavaScriptFunction>

        </Actions>

      </CommandDefinition>

    </CommandDefinitions>

    <RuleDefinitions>

      <TabDisplayRules />

      <DisplayRules>

        <DisplayRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.DisplayRule.RelationshipTypeRule">

          <RelationshipTypeRule RelationshipType="OneToMany" AppliesTo="SelectedEntity" />

        </DisplayRule>

        <DisplayRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.DisplayRule.EntityPrivilegeRule">

          <EntityPrivilegeRule AppliesTo="PrimaryEntity" PrivilegeDepth="Basic" PrivilegeType="AppendTo" />

        </DisplayRule>

        <DisplayRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.DisplayRule.EntityPrivilegeRule2">

          <EntityPrivilegeRule AppliesTo="SelectedEntity" PrivilegeDepth="Basic" PrivilegeType="Write" />

        </DisplayRule>

        <DisplayRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.DisplayRule.EntityPrivilegeRule3">

          <EntityPrivilegeRule AppliesTo="SelectedEntity" PrivilegeDepth="Basic" PrivilegeType="Append" />

        </DisplayRule>

      </DisplayRules>

      <EnableRules>

        <EnableRule Id="makeer.SubGrid.{!EntityLogicalName}.MainTab.Management.RemoveChildren.Command.EnableRule.SelectionCountRule">

          <SelectionCountRule AppliesTo="SelectedEntity" Minimum="1" Default="false" />

        </EnableRule>

      </EnableRules>

    </RuleDefinitions>

    <LocLabels>

      <LocLabel Id="makeer.SubGrid.EntityLogicalName.MainTab.Management.RemoveChildren.LabelText">

        <Titles>

          <Title languagecode="1033" description="Remove" />

        </Titles>

      </LocLabel>

      <LocLabel Id="makeer.SubGrid.EntityLogicalName.MainTab.Management.RemoveChildren.Description">

        <Titles>

          <Title languagecode="1033" description="Remove selected records. When you remove a record it still exists in the system." />

        </Titles>

      </LocLabel>

    </LocLabels>

  </RibbonDiffXml>

 

Conditional Hiding of Process Group in Ribbon on CRM 2011

On CRM 2011, there is Process Group in Ribbon. This Process groups contains 2 Ribbon Buttons namely “Run Workflow” and “Start Dialog”.

image

 

By configuring Security Roles we can disable these buttons from certain Users.

In Security Role, Entity Process should be configured to revoke read permissions

image

 

However in some cases we wanted to completely hide these buttons along with its Group. CRM does not hide the buttons even if we revoke Read permissions from Process entity. At the most it disables the button in Ribbon.

We can completely hide the Process group globally by using HideCustomAction, but then its is hidden from every user, even System Administrator cannot see it. So we have to do Ribbon customization to conditionally hide the Process group from certain User.

In following blog I have explain how I achieved it using entity based Ribbon customization.

Steps

1. Create a CRM Solution with number of entities on which Process Group in Ribbon needs to be hidden and Export this solution.

2. From exported .zip file extract customization.xml file and open it in suitable editor, my favorite is Visual Studio.

3. Under <CustomActions> which is under<RibbonDiffXml> paste following XML code

<!–********************************************–>

<CustomAction Id="Mscrm.HomepageGrid.XXXXX.MainTab.Workflow_CustomAction" Location="Mscrm.HomepageGrid.XXXXX.MainTab.Workflow" Sequence="40">

  <CommandUIDefinition>

    <Group Id="Mscrm.HomepageGrid.XXXXX.MainTab.Workflow" Command="Mscrm.XXXXX.Workflow.Command" Sequence="40" Title="$Resources:Ribbon.HomepageGrid.Data.Workflow" Image32by32Popup="/_imgs/ribbon/runworkflow32.png" Template="Mscrm.Templates.Flexible">

      <Controls Id="Mscrm.HomepageGrid.XXXXX.MainTab.Workflow.Controls">

        <Button Id="Mscrm.HomepageGrid.XXXXX.RunWorkflow" ToolTipTitle="$Resources:Ribbon.HomepageGrid.Data.Workflow.RunWorkflow" ToolTipDescription="$Resources(EntityDisplayName):Ribbon.Tooltip.RunWorkflow" Command="Mscrm.RunWorkflowSelected" Sequence="40" LabelText="$Resources:Ribbon.HomepageGrid.Data.Workflow.RunWorkflow" Alt="$Resources:Ribbon.HomepageGrid.Data.Workflow.RunWorkflow" Image16by16="/_imgs/ribbon/StartWorkflow_16.png" Image32by32="/_imgs/ribbon/runworkflow32.png" TemplateAlias="o1" />

        <Button Id="Mscrm.HomepageGrid.XXXXX.RunScript" ToolTipTitle="$Resources:Ribbon.HomepageGrid.Data.InteractiveWorkflow.RunScript" ToolTipDescription="$Resources(EntityDisplayName):Ribbon.Tooltip.RunScript" Command="Mscrm.RunInteractiveWorkflowSelected" Sequence="50" LabelText="$Resources:Ribbon.HomepageGrid.Data.InteractiveWorkflow.RunScript" Alt="$Resources:Ribbon.HomepageGrid.Data.InteractiveWorkflow.RunScript" Image16by16="/_imgs/ribbon/startdialog_16.png" Image32by32="/_imgs/ribbon/startdialog_32.png" TemplateAlias="o1" />

      </Controls>

    </Group>

  </CommandUIDefinition>

</CustomAction>

 

<CustomAction Id="Mscrm.Form.XXXXX.MainTab.Workflow_CustomAction" Location="Mscrm.Form.XXXXX.MainTab.Workflow">

  <CommandUIDefinition>

    <Group Id="Mscrm.Form.XXXXX.MainTab.Workflow" Command="Mscrm.XXXXX.Workflow.Command" Sequence="45" Title="$Resources:Ribbon.HomepageGrid.Data.Workflow" Image32by32Popup="/_imgs/ribbon/runworkflow32.png" Template="Mscrm.Templates.Flexible">

      <Controls Id="Mscrm.Form.XXXXX.MainTab.Workflow.Controls">

        <Button Id="Mscrm.Form.XXXXX.RunWorkflow" ToolTipTitle="$Resources:Ribbon.HomepageGrid.Data.Workflow.RunWorkflow" ToolTipDescription="$Resources(EntityDisplayName):Ribbon.Tooltip.RunWorkflow" Command="Mscrm.RunWorkflowPrimary" Sequence="20" LabelText="$Resources:Ribbon.HomepageGrid.Data.Workflow.RunWorkflow" Alt="$Resources:Ribbon.HomepageGrid.Data.Workflow.RunWorkflow" Image16by16="/_imgs/ribbon/StartWorkflow_16.png" Image32by32="/_imgs/ribbon/runworkflow32.png" TemplateAlias="o1" />

        <Button Id="Mscrm.Form.XXXXX.RunScript" ToolTipTitle="$Resources:Ribbon.HomepageGrid.Data.InteractiveWorkflow.RunScript" ToolTipDescription="$Resources(EntityDisplayName):Ribbon.Tooltip.RunScript" Command="Mscrm.RunInteractiveWorkflowPrimary" Sequence="25" LabelText="$Resources:Ribbon.HomepageGrid.Data.InteractiveWorkflow.RunScript" Alt="$Resources:Ribbon.HomepageGrid.Data.InteractiveWorkflow.RunScript" Image16by16="/_imgs/ribbon/startdialog_16.png" Image32by32="/_imgs/ribbon/startdialog_32.png" TemplateAlias="o1" />

      </Controls>

    </Group>

  </CommandUIDefinition>

</CustomAction>

 

<CustomAction Id="Mscrm.SubGrid.XXXXX.MainTab.Workflow_CustomAction" Location="Mscrm.SubGrid.XXXXX.MainTab.Workflow">

  <CommandUIDefinition>

    <Group Id="Mscrm.SubGrid.XXXXX.MainTab.Workflow" Command="Mscrm.XXXXX.Workflow.Command" Sequence="70" Title="$Resources:Ribbon.HomepageGrid.Data.Workflow" Image32by32Popup="/_imgs/ribbon/runworkflow32.png" Template="Mscrm.Templates.Flexible">

      <Controls Id="Mscrm.SubGrid.XXXXX.MainTab.Workflow.Controls">

        <Button Id="Mscrm.SubGrid.XXXXX.RunWorkflow" ToolTipTitle="$Resources:Ribbon.HomepageGrid.Data.Workflow.RunWorkflow" ToolTipDescription="$Resources(EntityDisplayName):Ribbon.Tooltip.RunWorkflow" Command="Mscrm.RunWorkflowSelected" Sequence="30" LabelText="$Resources:Ribbon.HomepageGrid.Data.Workflow.RunWorkflow" Alt="$Resources:Ribbon.HomepageGrid.Data.Workflow.RunWorkflow" Image16by16="/_imgs/ribbon/RunWorkflow_16.png" Image32by32="/_imgs/ribbon/runworkflow32.png" TemplateAlias="o1" />

        <Button Id="Mscrm.SubGrid.XXXXX.RunScript" ToolTipTitle="$Resources:Ribbon.HomepageGrid.Data.InteractiveWorkflow.RunScript" ToolTipDescription="$Resources(EntityDisplayName):Ribbon.Tooltip.RunScript" Command="Mscrm.RunInteractiveWorkflowSelected" Sequence="40" LabelText="$Resources:Ribbon.HomepageGrid.Data.InteractiveWorkflow.RunScript" Alt="$Resources:Ribbon.HomepageGrid.Data.InteractiveWorkflow.RunScript" Image16by16="/_imgs/ribbon/StartDialog_16.png" Image32by32="/_imgs/ribbon/StartDialog_32.png" TemplateAlias="o1" />

      </Controls>

    </Group>

  </CommandUIDefinition>

</CustomAction>

 

4. Replace XXXXX with entity logical name

5.Under <CommandDefinitions> paste following XML colde.

<!–********************************************–>

<CommandDefinition Id="Mscrm.XXXXX.Workflow.Command">

  <EnableRules>

    <EnableRule Id="Mscrm.Enabled" />

  </EnableRules>

  <DisplayRules>

    <DisplayRule Id="Mscrm.XXXXX.Workflow.DisplayRule"/>

  </DisplayRules>

  <Actions />

</CommandDefinition>

<!–********************************************–>

6. Replace XXXXX with entity logical name

7. Under <DisplayRules> under <RuleDefinitions> paste following XML code.

This rule basically checks whether User has Workflow Execution Permissions at Organization level. It will hide Process Group from all users who do not have Workflow Execution permissions.

<!–********************************************–>

<DisplayRule Id="Mscrm.XXXXX.Workflow.DisplayRule">

  <MiscellaneousPrivilegeRule PrivilegeName="WorkflowExecution" PrivilegeDepth="Global"/>

</DisplayRule>

<!–********************************************—>

8. Replace XXXXX with entity logical name

9. Save all these changes, Zip it back and Import the solution.

10. Open Security Role go to Customization tab and remove Execute Workflow Job access.

image

 

11. Now user will not see the Process tab

image

 

One of the advantage of this approach is Ribbon buttons are hidden based on Security Role settings. Based on requirements Display conditions can be further modified.

CRM 2011–Hiding Ribbon Tab using FormEntityContextRule

CRM 2011 does offer <HideCustomAction> to hide Ribbon Elements including Ribbon tabs. However in some cases we might have to selectively hide Tabs based on several conditions.

In such cases <HideCustomAction> is of less helpful as it globally hides the Ribbon element.

In this Walkthrough I am trying to demonstrate on of such scenario.

Scenario: A CRM Entity form has has Many-To-One relationship with multiple entities. Each of these entities are having Sub-Grid to show CRM entity in List form. Now we wanted to hide Contextual (List) tab for Sub-Grid entity from Main form of some of the entities.

Step 1:

Create a CRM 2011 Solution with entity for which Ribbon in Contextual Tab needs to be hidden. In this case entity name is new_cpvertical

Step 2:

Open ExportRibbonXml for the entity new_cpvertical. You can find this xml at SDK folder samplecodecsclientribbonexportribbonxmlbinDebugExportedRibbonXml. If RibbonXml for your entity is not available you can execute the solution exportribbonxml.sln and create one.

 

Step 3:

In RibbonXml, locate Tab element with Id as “Mscrm.SubGrid.new_cpvertical.MainTab” and copy entire Tab element with all its decendants.

Step 4:

Open customization.xml file from Solution you exported in Step1, and add Tab element copied in Step 3 under

image

Note that Location value for CustomAction is same as MainTab id.

Step 5:

Copy Command="Mscrm.SubGrid.new_cpvertical.MainTab" and all the Rules that has been referenced in Command.

Step 6:

Copy <TabDisplayRules> element under <RuleDefinitions> as shown below

image

Note: TabCommand should be same as CommandDefinition Id used under CommandDefinition

Step 7:

Create a new DisplayRule as shown below

image

Note: EntityName value is name of the PrimaryEntity where Sub-Grid is placed. Depending upon how many such entities need to be configured you can add Or to it.

Step 8:

Call this DisplayRule in Command ="Mscrm.SubGrid.new_cpvertical.MainTab"

image

Step 8:

Save the Customization, zip it back to Solution file and Import solution in CRM and Publish the Customization.

Here is how it looks with Contextual Ribbon hidden

With Contextual Ribbon (Note there is a List Tools Ribbon tab)

image

After hiding Contextual Ribbon (Note there is NO List Tools Ribbon tab)

image

Hope this helps

Walkthrough of Asynchronous call from <CustomRule> (RibbonDiff)

Walkthrough of Asynchronous call from <CustomRule>(RibbonDiff)

In following walkthrough I am demonstrating how to use EnableRule in Ribbon to call JavaScript function which will do Asynchronous call to CRM using REST and how to set Enable and Disable Ribbon Button

Assuming that Ribbon Customization is done and CustomRule is calling JavaScript function on EnableRule.

Following is how JavaScript. Here I am checking if logged in User is associated with current Entity record using Connections.

///<reference path=”XrmPage-vsdoc.js”/>
//Global Parameter
var isPartnerTeamMember = false;
var PageUI; 

function GetServerUrl() {

    var customServerURL = document.location.protocol + “//” + document.location.host + “/” + Xrm.Page.context.getOrgUniqueName();

    return customServerURL;

}

 

function CheckUserAccessOnPartner(PrimaryEntityTypeName, partnerObjectTypeCode, SelectedEntityTypeName, partnerId) {

    if (isPartnerTeamMember) {

        return true;

    }

    else {

        PageUI = window.top.opener.parent.Xrm.Page.ui;

        var context = Xrm.Page.context;

        var serverUrl = GetServerUrl();

        var userId = context.getUserId();

        var ODataPath = serverUrl + “/XRMServices/2011/OrganizationData.svc”;

        var retrieveReq = new XMLHttpRequest();

        retrieveReq.open(“GET”, ODataPath + “/ConnectionSet?$select=ConnectionId,Record1Id,Record1ObjectTypeCode,Record2Id,Record2ObjectTypeCode&$filter=Record1ObjectTypeCode/Value eq ” + partnerObjectTypeCode + ” and Record2ObjectTypeCode/Value eq 8 and Record1Id/Id eq guid'” + partnerId + “‘”, true);

        retrieveReq.setRequestHeader(“Accept”, “application/json”);

        retrieveReq.setRequestHeader(“Content-Type”, “application/json; charset=utf-8”);

        retrieveReq.onreadystatechange = function () {

            retrievePartnerConnectionsCallBack(this);

        };
        retrieveReq.send();
        return false;
    }
}

function retrievePartnerConnectionsCallBack(retrieveReq) {

    if (retrieveReq.readyState == 4 /* complete */) {

        if (retrieveReq.status == 200) {
            //Success
            var responseData = JSON.parse(retrieveReq.responseText).d;

            if (responseData != null && responseData.results != null && responseData.results.length > 0) {

                for (i = 0; i < responseData.results.length; i++) {

                    var record2Id = “{” + responseData.results[0].Record2Id.Id + “}”;

                    if (record2Id.toLowerCase() == Xrm.Page.context.getUserId().toLowerCase()) {

                        isPartnerTeamMember = true;

                        if (PageUI != null) {
                            PageUI.refreshRibbon();
                        }
                        break;
                    }
                }
            }
        }

        else {
            //errorHandler(retrieveReq);
        }
    }
}

 Key Point to note in above Script

  1. I am using Global variable to defined return value (isPartnerTeamMember) which is by default set to false.
  2. As first step I am checking isPartnerTeamMember and returning if it is true else calling REST query.
  3. In REST callback I am calling Xrm.Page.ui.refreshRibbon() method, which will reevaluate function and this time it will find isPartnerTeamMember as true and it will be returned from there.
  4. One of the important thing to note here is if you have having Ribbon button on SubGrid or associated view Xrm.Page.ui will be null for this you have to use window.top.opener.parent.Xrm.Page.ui (Thanks Daniel Cai !! for figuring out this)

Hope this Helps

CRM 2011 – Alternative to context.getServerUrl()

As all of you know context.getServerURL, returns the base server URL. The format of this Url can change depending on whether the user is connected to Microsoft Dynamics CRM 2011 On Premises, Microsoft Dynamics CRM Online, or working offline with Microsoft Dynamics CRM for Microsoft Office Outlook with Offline Access.

However there is one constrain using context.getServerURL as given in SDK Help –

The URL returned always returns the standard URL used to access the application. If you accessing the server locally using http://localhost or are using an IP address rather than the actual name of the server this will not be reflected in the value returned by this function. This means that if you are making web service calls or accessing Web resources Internet Explorer will apply security settings that apply to requests that cross domains. To avoid this, always connect to Microsoft Dynamics CRM using the standard URL.

So if user is accessing CRM using IP address, any call made to CRM using REST endpoint or SOAP endpoint will throw “Access Denied” error.

I have adopted following workaround for this problem which let user use ServerName as well as IP address for accessing CRM

Note: This approach does not work in CRM Online, you might have to tweek in little bit to get it work in CRM Online

Basically I am by passing context.getServerURL call and constructing ServerURL using Document.location property

var customServerURL = document.location.protocol + “//” + document.location.host + “/” + Xrm.Page.context.getOrgUniqueName();

For Silverlight Web Resources it is

stringserverUrl = string.Empty;
string orgUniqueName = string.Empty;
orgUniqueName = (string)GetContext().Invoke(“getOrgUniqueName”);

if (HtmlPage.Document.DocumentUri.Port != 80 && HtmlPage.Document.DocumentUri.Port != -1)
{
serverUrl =string.Format(“{0}://{1}:{2}/{3}”, HtmlPage.Document.DocumentUri.Scheme, HtmlPage.Document.DocumentUri.Host, HtmlPage.Document.DocumentUri.Port.ToString(), orgUniqueName);
}
else
{
serverUrl =string.Format(“{0}://{1}/{2}”, HtmlPage.Document.DocumentUri.Scheme, HtmlPage.Document.DocumentUri.Host, orgUniqueName);
}

Hope this helps