Why is my Ribbon Button Disabled? (or CRM2011 caching under the covers)

  Background Today I had a most frustrating problem where my Ribbon button was visible but disabled no matter what I did to the Command. This is usually caused by the Command missing or being invalid - but on further inspection of the RibbonLayout.js.aspx being loaded, the Command simply wasn't included in the initialisation objects. IISRESET just doesn't cut it! After a performing an IISRESET, the button was then enabled and the command was then included in the RibbonLayout.js. IISRESET clears the server cache so I guessed the problem was down to caching - but I didn't want to be doing this every time I published. So I investigated further. I soon discovered that the issue was that the reference to to the RibbonLayout.js included two query string parameter rver and mver along the lines of : /controls/ribbon/RibbonLayout.js.aspx? hierarchy=9886ead0-4fcc-4747-9a18-08e7a9a6de71 &id=EntityTemplateTab.lead.NoRelationship.Form.Mscrm.Form.lead.MainTab &ise=1 &lcid=1033 &mver=830776802 &oc=0 &rver=-1307274159 &ver=-986909320 When I published my customisations, the mver (MetaData version) was incrementing, but the rver (Ribbon Version) was not.  So for some reason the publish was not incrementing the ribbon version, and so my new command was not being loaded into the browser. The CRM 2011 cache is stored in the standard ASP.NET HTTP Cache - and it was this cache that holds the Ribbon Version. The publish is meant to invalidate this cache so that the new version can be used. Without this cache, every single request would have to hit the database to load customisations xml - so it is a very important part of the framework - but if it does not refresh automatically when changes are made - then the user interface is effectively stuck at a previous version. Note: I also discovered that the cache was on a sliding 60 minutes refresh - so If I advanced the time by 1 hour, I would also see my new ribbon customisations and the rver number incremented. The rver is not actually an incremental number, but a hash of all of the 'last published' dates of the ribbon customisations. The main thing is that it is a unique key that points to the correct cache item. So...Why was the cache not being invalidated? Upon investigating how the CRM2011 cache is managed - there is a Notifications table in the MSCRMCONFIG database that includes entries that instruct CRM to clear cache items. The Async Server picks these up and notifies the HTTP Worker processes to drop each cache by Key. You can see what's going on for your server when you publish by using the following SQL: CREATE TABLE #eventNames (eventId int, eventName nvarchar(2000)) INSERT INTO #eventNames (eventId, eventName) VALUES (0,'netSecurityUser') INSERT INTO #eventNames (eventId, eventName) VALUES (1,'netSecurityBusiness') INSERT INTO #eventNames (eventId, eventName) VALUES (2,'netSecurityOrganization') INSERT INTO #eventNames (eventId, eventName) VALUES (3,'netMetadata') INSERT INTO #eventNames (eventId, eventName) VALUES (4,'netMetadataEntity') INSERT INTO #eventNames (eventId, eventName) VALUES (5,'netSettingsOrganization') INSERT INTO #eventNames (eventId, eventName) VALUES (6,'netSettingsUser') INSERT INTO #eventNames (eventId, eventName) VALUES (7,'netSchedulingEngineInvalidateContainerCache') INSERT INTO #eventNames (eventId, eventName) VALUES (8,'netSchedulingEngineInvalidateResourceExpansionCache') INSERT INTO #eventNames (eventId, eventName) VALUES (9,'netSchedulingEngineInvalidateScheduleCache') INSERT INTO #eventNames (eventId, eventName) VALUES (12,'netSqmSessionReset') INSERT INTO #eventNames (eventId, eventName) VALUES (13,'netSiteMap') INSERT INTO #eventNames (eventId, eventName) VALUES (14,'netHardExpiry') INSERT INTO #eventNames (eventId, eventName) VALUES (15,'netInfo') INSERT INTO #eventNames (eventId, eventName) VALUES (16,'netMessageProcessorInvalidatePipelineCache') INSERT INTO #eventNames (eventId, eventName) VALUES (17,'netSavedQuery') INSERT INTO #eventNames (eventId, eventName) VALUES (18,'netPluginTypeInvalidateCache') INSERT INTO #eventNames (eventId, eventName) VALUES (19,'netPluginAssemblyInvalidateCache') INSERT INTO #eventNames (eventId, eventName) VALUES (20,'netStepDescriptionInvalidateCache') INSERT INTO #eventNames (eventId, eventName) VALUES (21,'netStepImageDescriptionInvalidateCache') INSERT INTO #eventNames (eventId, eventName) VALUES (22,'netConfigCreate') INSERT INTO #eventNames (eventId, eventName) VALUES (23,'netConfigUpdate') INSERT INTO #eventNames (eventId, eventName) VALUES (24,'netConfigDelete') INSERT INTO #eventNames (eventId, eventName) VALUES (25,'netConfigAll') INSERT INTO #eventNames (eventId, eventName) VALUES (26,'netScaleGroupSetState') INSERT INTO #eventNames (eventId, eventName) VALUES (27,'netServerSetState') INSERT INTO #eventNames (eventId, eventName) VALUES (28,'netEncryptionConfigurationChange') INSERT INTO #eventNames (eventId, eventName) VALUES (29,'netOrganizationCreate') INSERT INTO #eventNames (eventId, eventName) VALUES (30,'netOrganizationSetState') INSERT INTO #eventNames (eventId, eventName) VALUES (31,'netOrganizationUpdate') INSERT INTO #eventNames (eventId, eventName) VALUES (32,'netOrganizationDelete') INSERT INTO #eventNames (eventId, eventName) VALUES (33,'netSubscriptionEntity') INSERT INTO #eventNames (eventId, eventName) VALUES (34,'netOutlookFilter') INSERT INTO #eventNames (eventId, eventName) VALUES (35,'netCacheItemRemove') INSERT INTO #eventNames (eventId, eventName) VALUES (36,'netStepSecureConfigInvalidateCache') INSERT INTO #eventNames (eventId, eventName) VALUES (37,'netTransactionCurrency') INSERT INTO #eventNames (eventId, eventName) VALUES (38,'netLocator') INSERT INTO #eventNames (eventId, eventName) VALUES (39,'netSchedulingEngineMustRunPublishResourceGroupExpansion') INSERT INTO #eventNames (eventId, eventName) VALUES (40,'netOrganizationMove') INSERT INTO #eventNames (eventId, eventName) VALUES (41,'netUserInvitationUpdate') INSERT INTO #eventNames (eventId, eventName) VALUES (42,'netSolution') INSERT INTO #eventNames (eventId, eventName) VALUES (43,'netServerCreate') INSERT INTO #eventNames (eventId, eventName) VALUES (44,'netServerMove') INSERT INTO #eventNames (eventId, eventName) VALUES (45,'netServerDelete') INSERT INTO #eventNames (eventId, eventName) VALUES (46,'netSecurityTeam') INSERT INTO #eventNames (eventId, eventName) VALUES (47,'netRibbonCustomizationUpdate') INSERT INTO #eventNames (eventId, eventName) VALUES (48,'netDependencyNode') INSERT INTO #eventNames (eventId, eventName) VALUES (50,'netDnsUpdate') INSERT INTO #eventNames (eventId, eventName) VALUES (51,'netSystemForm') INSERT INTO #eventNames (eventId, eventName) VALUES (52,'netSrsExtensionsInstalled') INSERT INTO #eventNames (eventId, eventName) VALUES (53,'netFederationProviderCreate') INSERT INTO #eventNames (eventId, eventName) VALUES (54,'netFederationProviderUpdate') INSERT INTO #eventNames (eventId, eventName) VALUES (55,'netFederationProviderDelete') INSERT INTO #eventNames (eventId, eventName) VALUES (56,'netCertificateCreate') INSERT INTO #eventNames (eventId, eventName) VALUES (57,'netCertificateUpdate') INSERT INTO #eventNames (eventId, eventName) VALUES (58,'netCertificateDelete') INSERT INTO #eventNames (eventId, eventName) VALUES (59,'netWebResource') INSERT INTO #eventNames (eventId, eventName) VALUES (60,'netSolutionImportFailure') INSERT INTO #eventNames (eventId, eventName) VALUES (62,'netFlushRecordCountSnapshotCache') INSERT INTO #eventNames (eventId, eventName) VALUES (63,'netFlushPrincipalObjectAccessReadSnapshotCache') INSERT INTO #eventNames (eventId, eventName) VALUES (64,'netMaxEventCount')

SELECT n.CreatedOn, l.eventName, n.EventData ,n.EventId, n.OrganizationId FROM MSCRM_CONFIG.dbo.Notification n LEFT OUTER JOIN #eventNames l ON n.EventId = l.eventId ORDER BY CreatedOn desc

The reason that my notifications were not getting through was that I had done some testing previously where the server time had been advanced a month. This meant that there were notifications sitting in this table queue that were for a month time. The Async server seems to use the latest date when it first starts to define a time window to query for new notifications. Obviously, in my case, the clear cache notifications (or netRibbonCustomizationUpdate events) were not being processed. I performed an unsupported DELETE from the MSCRM_CONFIG.dbo.Notifications table and all was well again. (phew!) I hope this has provided you with a bit of an insight into how CRM2011 caching works and how you can work out what's going on if you find things not being reflected when you make changes to customisations.

 

Test your security configuration with "Run as different user"

I few people I've shown this tip to recently have been surprised - so I thought I'd share it.  If you are developing any Dynamics CRM Solution, you are always going to want to test out security and role based access. The simplest way of doing this is to log on as multiple users, each with a different role, to see the effect of your security configuration changes. This can easily be achieved by using the 'Extended Context Menu' option 'Run as different user' 1) Hold down the Shift Key whilst 'Right Clicking' on Internet Explorer 2) You will see the 'Extended Context Menu':

3) Use the 'Run as different user' option to start a new Internet Explorer session as a different CRM User. Simple but effective!

The object cannot be updated because it is read-only

When re-activating a case in Dynamics CRM 2011 via the Form Ribbon button, you may recieve the message: The object cannot be updated because it is read-only The usual cause of this issue is a piece of javascript in the form onload event changing the value of a field or setting the submit mode to 'always'. I recently discovered another reason why this can happen: If you have a N:1 lookup field on a form, and the related record's name attribute has a trailing space in the name field (e.g. 'A record ') , the lookup attribute will always be dirty on the referencing form, irrespective of if it has changed or not.  So when you try and re-activate a case, the form will be submitted with the lookup field appearing as though it has been edited, and you will receive the message from the server indicating that the record cannot be updated. To resolve this issue, simply remove the trailing space! Normally, field values will have trailing spaces stripped off when saved via a form, but this issue can be caused by data being imported with trailing spaces in the field values. Hope this helps!  

QualifyLead PlugIn

Supposing you needed some custom logic to happen immediately after a lead was qualified. You can achieve this by registering a Plug-in on the QualifyLead Post Operation stage. From within this Plug-in you can easily get a reference to the newly created Account, Contact and Opportunity and make any changes you need. Inside the PlugIn you need the following code:

IOrganizationService service = localContext.OrganizationService; // Get the qualified lead EntityReference leadid = (EntityReference) localContext.PluginExecutionContext.InputParameters["LeadId"]; Lead lead = (Lead)service.Retrieve(leadid.LogicalName, leadid.Id,new ColumnSet(LeadAttributes.crm_AccountType));

// Get the newly created account, contact, opportunity Contact contact = null; Opportunity opportunity = null; Account account = null; foreach (EntityReference created in (IEnumerable<object>) localContext.PluginExecutionContext.OutputParameters["CreatedEntities"]) { switch (created.LogicalName) { case Contact.EntityLogicalName: contact = (Contact)service.Retrieve(Contact.EntityLogicalName, created.Id, new ColumnSet(true)); break; case Account.EntityLogicalName: account = (Account)service.Retrieve(Account.EntityLogicalName, created.Id, new ColumnSet(true)); break; case Opportunity.EntityLogicalName: opportunity = (Opportunity)service.Retrieve(Opportunity.EntityLogicalName, created.Id, new ColumnSet(true)); break;

}

}

If you need to make any changes to the created records, you can simply use:

contact.LastName = "some change"; service.Update(contact);

Hope this helps.

Re-arrange standard Ribbon Buttons by customising Ribbon Group

Ribbon Groups are sets of Ribbon controls that change their layout to fit the available window width. Buttons will change size and position as defined by the template and scale settings. If you need to move buttons around within a stanard group so that the buttons used most often are always visible, you must customise the whole group rather than just the individual buttons. The Ribbon Workbench for CRM 2011 makes customising groups simple. The following video shows you how to: 1. Customise a Group2. Move buttons around in the group3. Move buttons between template locations4. Add a menu item to an existing split button in the group The tutorial assumes you have already installed the Ribbon Workbench as shown by this short video.

The resulting RibbonXml is:   <?xml version="1.0" encoding="utf-16"?> <RibbonDiffXml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <CustomActions> <CustomAction Id="demo.Mscrm.HomepageGrid.contact.MainTab.Management.CustomAction" Location="Mscrm.HomepageGrid.contact.MainTab.Groups.children" Sequence="10"> <CommandUIDefinition> <Group Command="Mscrm.Enabled" Description="$Resources:Ribbon.HomepageGrid.MainTab.Management" Id="Mscrm.HomepageGrid.contact.MainTab.Management" Image32by32Popup="/imgs/ribbon/newrecord32.png" Sequence="10" Template="Mscrm.Templates.FourOverflow" Title="$Resources:Ribbon.HomepageGrid.MainTab.Management"> <Controls Id="Mscrm.HomepageGrid.contact.MainTab.Management.Controls"> <Button Alt="$Resources:Ribbon.HomepageGrid.account.Record.Merge.MergeRecords" Command="Mscrm.HomepageGrid.contact.MergeRecords" Id="Mscrm.HomepageGrid.contact.MergeRecords" Image32by32="/imgs/ribbon/MergeRecords32.png" Image16by16="/imgs/ribbon/MergeRecords16.png" LabelText="$Resources:Ribbon.HomepageGrid.account.Record.Merge.MergeRecords" Sequence="10" TemplateAlias="isv" ToolTipTitle="$Resources:Ribbon.HomepageGrid.account.Record.Merge.MergeRecords" ToolTipDescription="$Resources:Ribbon.Tooltip.Merge" /> <SplitButton Alt="$Resources:Ribbon.HomepageGrid.MainTab.Management.Delete" Command="Mscrm.HomepageGrid.DeleteSplitButtonCommand" Id="Mscrm.HomepageGrid.contact.DeleteMenu" Image32by32="/imgs/Workplace/remove32.png" Image16by16="/imgs/ribbon/delete16.png" LabelText="$Resources:Ribbon.HomepageGrid.MainTab.Management.Delete" Sequence="15" TemplateAlias="o2" ToolTipTitle="$Resources:MscrmHomepageGridOtherMainTabManagementDeleteToolTipTitle" ToolTipDescription="$Resources(EntityPluralDisplayName):Ribbon.Tooltip.Delete"> <Menu Id="Mscrm.HomepageGrid.contact.DeleteMenu.Menu"> <MenuSection Id="Mscrm.HomepageGrid.contact.DeleteMenu.MenuSection" Sequence="10" DisplayMode="Menu16"> <Controls Id="Mscrm.HomepageGrid.contact.DeleteMenu.Controls"> <Button Command="demo.contact.DeleteSomething.Command" Id="demo.contact.Button1.Button" Image16by16="$webresource:newImage16.png" LabelText="$LocLabels:demo.contact.Button1.Button.LabelText" Sequence="30" /> <Button Alt="$Resources:Ribbon.HomepageGrid.MainTab.Management.Delete" Command="Mscrm.DeleteSelectedRecord" Id="Mscrm.HomepageGrid.contact.Delete" Image32by32="/imgs/Workplace/remove32.png" Image16by16="/imgs/ribbon/delete16.png" LabelText="$Resources:Ribbon.HomepageGrid.MainTab.Management.Delete" Sequence="50" ToolTipTitle="$Resources:MscrmHomepageGridOtherMainTabManagementDeleteToolTipTitle" ToolTipDescription="$Resources(EntityPluralDisplayName):Ribbon.Tooltip.Delete" /> <Button Command="Mscrm.HomepageGrid.BulkDelete" Id="Mscrm.HomepageGrid.contact.BulkDelete" Image32by32="/imgs/ribbon/BulkDeleteWizard32.png" Image16by16="/imgs/ribbon/BulkDeleteWizard16.png" LabelText="$Resources:Ribbon.HomepageGrid.MainTab.Management.BulkDelete" Sequence="100" ToolTipTitle="$Resources:Ribbon.HomepageGrid.MainTab.Management.BulkDelete" ToolTipDescription="$Resources:Ribbon.HomepageGrid.MainTab.Management.BulkDelete.TooltipDescription" /> </Controls> </MenuSection> </Menu> </SplitButton> <Button Alt="$Resources:Ribbon.HomepageGrid.MainTab.Management.Edit" Command="Mscrm.EditSelectedRecord" Id="Mscrm.HomepageGrid.contact.Edit" Image32by32="/imgs/ribbon/edit32.png" Image16by16="/imgs/ribbon/Edit16.png" LabelText="$Resources:Ribbon.HomepageGrid.MainTab.Management.Edit" Sequence="20" TemplateAlias="o1" ToolTipTitle="$Resources:Ribbon.HomepageGrid.MainTab.Management.Edit" ToolTipDescription="$Resources(EntityDisplayName):Ribbon.Tooltip.Edit" /> <Button Alt="$Resources:Ribbon.HomepageGrid.MainTab.New" Command="Mscrm.NewRecordFromGrid" Id="Mscrm.HomepageGrid.contact.NewRecord" Image32by32="/imgs/ribbon/newrecord32.png" Image16by16="/imgs/ribbon/NewRecord16.png" LabelText="$Resources:Ribbon.HomepageGrid.MainTab.New" Sequence="30" TemplateAlias="o1" ToolTipTitle="$Resources:Ribbon.HomepageGrid.MainTab.New" ToolTipDescription="$Resources(EntityDisplayName):Ribbon.Tooltip.New" /> <Button Alt="$Resources:Ribbon.HomepageGrid.account.Record.Status.Deactivate" Command="Mscrm.HomepageGrid.Deactivate" Id="Mscrm.HomepageGrid.contact.Deactivate" Image32by32="/imgs/ribbon/Deactivate32.png" Image16by16="/imgs/ribbon/deactivate16.png" LabelText="$Resources:Ribbon.HomepageGrid.account.Record.Status.Deactivate" Sequence="40" TemplateAlias="o2" ToolTipTitle="$Resources:Ribbon.HomepageGrid.account.Record.Status.Deactivate" ToolTipDescription="$Resources(EntityPluralDisplayName):Ribbon.Tooltip.Deactivate" /> <Button Alt="$Resources:Ribbon.HomepageGrid.account.Record.Status.Activate" Command="Mscrm.HomepageGrid.Activate" Id="Mscrm.HomepageGrid.contact.Activate" Image32by32="/imgs/ribbon/Activate32.png" Image16by16="/imgs/ribbon/Activate16.png" LabelText="$Resources:Ribbon.HomepageGrid.account.Record.Status.Activate" Sequence="50" TemplateAlias="o2" ToolTipTitle="$Resources:Ribbon.HomepageGrid.account.Record.Status.Activate" ToolTipDescription="$Resources(EntityPluralDisplayName):Ribbon.Tooltip.Activate" /> <FlyoutAnchor Alt="$Resources:Ribbon.HomepageGrid.account.Record.Dupe.Detect" Command="Mscrm.HomepageGrid.DetectDupes" Id="Mscrm.HomepageGrid.contact.Detect" Image16by16="/imgs/ribbon/DetectDuplicates16.png" Image32by32="/imgs/ribbon/DuplicateDetection32.png" LabelText="$Resources:Ribbon.HomepageGrid.account.Record.Dupe.Detect" Sequence="60" TemplateAlias="o3" ToolTipTitle="$Resources:Ribbon.HomepageGrid.account.Record.Dupe.Detect" ToolTipDescription="$Resources(EntityPluralDisplayName):Ribbon.Tooltip.DetectDuplicates"> <Menu Id="Mscrm.HomepageGrid.contact.Detect.Menu"> <MenuSection Id="Mscrm.HomepageGrid.contact.Detect.MenuSection" Sequence="10" DisplayMode="Menu16"> <Controls Id="Mscrm.HomepageGrid.contact.Detect.Controls"> <Button Alt="$Resources:Ribbon.HomepageGrid.account.Record.Dupe.Detect.Selected" Command="Mscrm.HomepageGrid.DetectDupesSelected" Id="Mscrm.HomepageGrid.contact.Detect.Selected" Image32by32="/imgs/ribbon/DeleteSelected32.png" Image16by16="/imgs/ribbon/DeleteSelected16.png" LabelText="$Resources:Ribbon.HomepageGrid.account.Record.Dupe.Detect.Selected" Sequence="10" ToolTipTitle="$Resources:MscrmHomepageGridOtherMainTabManagementDetectSelectedToolTipTitle" ToolTipDescription="$Resources:MscrmHomepageGridOtherMainTabManagementDetectSelectedToolTipDescription" /> <Button Alt="$Resources:Ribbon.HomepageGrid.account.Record.Dupe.Detect.All" Command="Mscrm.HomepageGrid.DetectDupesAll" Id="Mscrm.HomepageGrid.contact.Detect.All" Image32by32="/imgs/ribbon/DetectAll32.png" Image16by16="/imgs/ribbon/DetectAll16.png" LabelText="$Resources:Ribbon.HomepageGrid.account.Record.Dupe.Detect.All" Sequence="20" ToolTipTitle="$Resources:MscrmHomepageGridOtherMainTabManagementDetectAllToolTipTitle" ToolTipDescription="$Resources(EntityDisplayName):MscrmHomepageGridEntityLogicalNameMainTabManagementDetectAllToolTipDescription" /> </Controls> </MenuSection> </Menu> </FlyoutAnchor> </Controls> </Group> </CommandUIDefinition> </CustomAction> <CustomAction Id="demo.Mscrm.HomepageGrid.contact.MainTab.Management.MaxSize.CustomAction" Location="Mscrm.HomepageGrid.contact.MainTab.Scaling.children" Sequence="10"> <CommandUIDefinition> <MaxSize GroupId="Mscrm.HomepageGrid.contact.MainTab.Management" Id="Mscrm.HomepageGrid.contact.MainTab.Management.MaxSize" Sequence="10" Size="LargeMediumLargeMedium" /> </CommandUIDefinition> </CustomAction> <CustomAction Id="demo.Mscrm.HomepageGrid.contact.MainTab.Management.Scale.1.CustomAction" Location="Mscrm.HomepageGrid.contact.MainTab.Scaling.children" Sequence="130"> <CommandUIDefinition> <Scale GroupId="Mscrm.HomepageGrid.contact.MainTab.Management" Id="Mscrm.HomepageGrid.contact.MainTab.Management.Scale.1" Sequence="130" Size="LargeMediumLargeMedium" /> </CommandUIDefinition> </CustomAction> <CustomAction Id="demo.Mscrm.HomepageGrid.contact.MainTab.Management.Scale.2.CustomAction" Location="Mscrm.HomepageGrid.contact.MainTab.Scaling._children" Sequence="170"> <CommandUIDefinition> <Scale GroupId="Mscrm.HomepageGrid.contact.MainTab.Management" Id="Mscrm.HomepageGrid.contact.MainTab.Management.Scale.2" Sequence="170" Size="Popup" /> </CommandUIDefinition> </CustomAction> </CustomActions> <Templates> <RibbonTemplates Id="Mscrm.Templates" /> </Templates> <CommandDefinitions> <CommandDefinition Id="demo.contact.DeleteSomething.Command"> <EnableRules /> <DisplayRules /> <Actions> <Url Address="about:blank" WinMode="1" /> </Actions> </CommandDefinition> </CommandDefinitions> <RuleDefinitions> <TabDisplayRules /> <DisplayRules /> <EnableRules /> </RuleDefinitions> <LocLabels> <LocLabel Id="demo.contact.Button1.Button.LabelText"> <Titles> <Title description="Delete Something" languagecode="1033" /> </Titles> </LocLabel> </LocLabels> </RibbonDiffXml>

Watch out for an upcomming tutorial on creating new tabs and changing the scaling behaviour of groups. I hope you agree that using the Ribbon Workbench can shave hours off of your development time when customising the CRM2011 Ribbon! The Ribbon Workbench for CRM 2011 is a free download. If you find an issue with the Ribbon Workbench for Dynamics CRM 2011 or have any suggestions, please send it here.  Happy Ribbon Customising!  

Dynamically Enable/Disable a standard ribbon button based on a form value

There are occations when you need to change the command of an existing ribbon button, without changing the actual button its self. In the Ribbon Xml the Command and the Button are logically separated so that the Command can be used on more than one button. The Ribbon Workbench for CRM 2011 allows you to easily select a standard button and 'Customise Command'. This creates a copy of the standard command along with enable/display rules for you to modify. The following video shows the Run Workflow command being enabled/disabled based on a value on the form. Javascript is used to make a call to refreshRibbon when the form value changes. The tutorial assumes you have already installed the Ribbon Workbench as shown by this short video.

  The resulting RibbonXml is: <RibbonDiffXml xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <CustomActions /> <Templates> <RibbonTemplates Id="Mscrm.Templates" /> </Templates> <CommandDefinitions> <CommandDefinition Id="Mscrm.RunWorkflowPrimary"> <EnableRules> <EnableRule Id="Mscrm.FormStateNotNew" /> <EnableRule Id="Mscrm.RunWorkflowPrimary" /> <EnableRule Id="new.contact.FirstnameNotEmpty.EnableRule" /> </EnableRules> <DisplayRules /> <Actions> <JavaScriptFunction FunctionName="Mscrm.FormAction.launchOnDemandWorkflowForm" Library="/static/forms/form.js"> <CrmParameter Value="PrimaryEntityTypeCode" /> <StringParameter Value="" /> </JavaScriptFunction> </Actions> </CommandDefinition> </CommandDefinitions> <RuleDefinitions> <TabDisplayRules /> <DisplayRules /> <EnableRules> <EnableRule Id="Mscrm.FormStateNotNew"> <FormStateRule State="Create" InvertResult="true" /> </EnableRule> <EnableRule Id="Mscrm.RunWorkflowPrimary"> <CustomRule FunctionName="Mscrm.RibbonActions.enableWorkflowOnForm" Library="/static/common/scripts/RibbonActions.js" /> </EnableRule> <EnableRule Id="new.contact.FirstnameNotEmpty.EnableRule"> <ValueRule Field="firstname" Value="null" InvertResult="true" /> </EnableRule> </EnableRules> </RuleDefinitions> <LocLabels /> </RibbonDiffXml>

  And the refresh ribbon javascript  function refreshRibbonOnChange() { Xrm.Page.ui.refreshRibbon(); }

  I hope you agree that using the Ribbon Workbench can shave hours off of your development time when customising the CRM2011 Ribbon! The Ribbon Workbench for CRM 2011 is a free download. If you find an issue with the Ribbon Workbench for Dynamics CRM 2011 or have any suggestions, please send it here.  Happy Ribbon Customising!  

CRM 2011 Update Roll Up 6 (UR6) "Existing SQL Server connections to the Microsoft Dynamics CRM databases must be closed before setup can continue."

I've installed UR6 on a couple of server farms so far with not issues but today I encountered a problem that I'd not seen before. The server UR6 install worked fine, but the Organisation still showed version 5.0.9688.1533 which is UR5 rather than the expected 5.0.9690.1992. Using File->About Microsoft Dynamics CRM, the version showed as (5.0.9690.1992) (DB 5.0.9688.1533) Updating should have been simply a case of using Deployment Manager and selecting 'Update' on the Organisation but it gave the following error: Existing SQL Server connections to the Microsoft Dynamics CRM databases must be closed before setup can continue. I tried the following with no success: 1. Restarted SQL Server 2. Rebooted SQL Server 3. iisreset on the Application Server The fix was to stop the Async services and sandbox service. Then the database update went through with no problems.   UPDATE: The server needed an iisreset before the About dialog reflected the new database version.  

iPad killed the Silverlight star?

After the initial Statement of Direction in May and then the interview with Brad Wilson the then general manager of Dynamics CRM, there has been quite some excitement about cross-browser support for Dynamics CRM 2011. The timescale given was "the first half of 2012" being release with UR8. The thought of access to Dynamics CRM from an iPad is quite an attractive prospect I have to admit - and I'm really excited to see how Microsoft are going to structure the CRM user interface when they deliver it through cross browser HTML5.

Are we going to get a single cross browser interface, or will there still be a IE specific one with richer functionality? Will the cross browser support be more like the mobile client - with it's own form design - Will IE still be the preferred browser? You will still need IE to use the Outlook Client so will the cross browser support be more of a 'selling point' than something that actually makes a significant difference to business users?

  Cross browser HTML5 will mean replacing the IE specific .htc 'behaviours' which is quite a considerable piece of work - but a subject closer to my heart is the future of Silverlight in CRM2011. Silverlight is supported on most modern browsers (http://www.microsoft.com/getsilverlight/get-started/install/default.aspx#) but not on iPads. Could this mean an end to Silverlight WebResources just so that we can get iPad support? Only time will tell, but it might make it harder to choose the developer productivity and user experience gains that come with Silverlight. The iPad is not a desktop replacement. There are limitations with SalesForce.com on the iPad (http://www.crmverse.com/using-salesforce-com-on-ipad/). Unless a client has decided to use iPads as a primary user interface device, I am still recomending developing Silverlight WebResources but structuring them in a way that will make it easier to port them to an HTML5 user interface when the developer tooling support is improved. Maybe with Windows 8 Tablets the problem will go away...then again...maybe not! References:

http://www.develop1.net/public/post/The-future-of-Silverlight-for-Dynamics-CRM.aspx http://bit.ly/wMgudx http://bit.ly/xuncvh http://bit.ly/tdlDvj http://bit.ly/r4Iw0D

Adding an Advanced Find Query to a Form Sub grid

I've yet to see a clear post on how to add an Advanced Find Query as a subgrid on an entity form for CRM 2011. This is something that was quite common in CRM 4 due to the lack of sub grid support - but with CRM 2011 occationally we come up against the limitation of only showing related records and fields of only a single relationship away from the root entity. Here are the steps: 1. Find the SavedQueryId of the default advanced find view for the entity you are reporting on. Use the following SQL against the MSCRM database to find it: Select Name,SavedQueryId,ReturnedTypeCode,FetchXml,LayoutXml from SavedQuery where QueryType=1 and IsDefault=1Order by ReturnedTypeCode 2. Create a webresource named 'new_SubGridFetchXml.htm' of type 'Web Page (HTML)' Provide the following html:

<HTML><HEAD><TITLE></TITLE> <SCRIPT type=text/javascript src="ClientGlobalContext.js.aspx"></SCRIPT> <SCRIPT type=text/javascript> function submitForm() { var form = document.forms[0]; var context = GetGlobalContext(); form.action = context.getServerUrl() + '/AdvancedFind/fetchData.aspx'; form.LayoutXml.value ='<LAYOUTXML>'; form.FetchXml.value = '<FETCHXML>' form.submit(); } </SCRIPT> <META charset=utf-8></HEAD> <BODY onload=submitForm()> <FORM method=post action=""> <INPUT name=FetchXml type=hidden> <INPUT name=LayoutXml type=hidden> <INPUT name=EntityName value=contact type=hidden> <INPUT name=DefaultAdvFindViewId value=<SavedQueryID> type=hidden> <INPUT name=ViewId value=<SavedQueryID> type=hidden> <INPUT name=ViewType value=4230 type=hidden> <INPUT name=SortCol value=<SortColumnLogicalName>:1; type=hidden> <INPUT name=UIProvider type=hidden> <INPUT name=DataProvider type=hidden> </FORM></BODY></HTML>

Adjust the <SavedQueryID> reference to the default advanced find query id found in step 1. Adjust the <SortColumnLogicalName> to be the logical name of the field you want to sort by. Replace the LayoutXml and FetchXml strings with the values returned from the query in 1 - you can then adjust them to suit your particular needs. IMPORTANT: If you add the web resource to a sub 'virtual path', you must alter the reference to the ClientGlobalContext.js.aspx to include the corresponding number of '../' Don't worry about the script error when saving the page in the WebResource editor. Your finished page would look something like

<HTML><HEAD><TITLE></TITLE> <SCRIPT type=text/javascript src="ClientGlobalContext.js.aspx"></SCRIPT> <SCRIPT type=text/javascript> function submitForm() { var form = document.forms[0]; var context = GetGlobalContext(); form.action = context.getServerUrl() + '/AdvancedFind/fetchData.aspx'; form.LayoutXml.value ='<grid name="resultset" object="2" jump="lastname" select="1" icon="1" preview="1"><row name="result" id="contactid"><cell name="fullname" width="300" /><cell name="telephone1" width="125" /></row></grid>'; form.FetchXml.value = '<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"><entity name="contact"><attribute name="fullname"/><attribute name="telephone1"/><attribute name="contactid"/><order attribute="fullname" descending="false"/></entity></fetch>'; form.submit(); } </SCRIPT> <META charset=utf-8></HEAD> <BODY onload=submitForm()> <FORM method=post action=""> <INPUT name=FetchXml type=hidden> <INPUT name=LayoutXml type=hidden> <INPUT name=EntityName value=contact type=hidden> <INPUT name=DefaultAdvFindViewId value={00000000-0000-0000-00AA-000000666400} type=hidden> <INPUT name=ViewId value={00000000-0000-0000-00AA-000000666400} type=hidden> <INPUT name=ViewType value=4230 type=hidden> <INPUT name=SortCol value=fullname:1; type=hidden> <INPUT name=UIProvider type=hidden> <INPUT name=DataProvider type=hidden> </FORM></BODY></HTML>

  1. Add the webresource to the form that you want to show it on.
  2. Publish and Go! Usual disclaimers apply - and this is not a fully supported solution since it uses un-documented functionality