Thursday, October 2, 2014

Manipulating Business Process Flow Fields with Business Rules

This is a re-post from a colleague - credit must go to Martin Miller at OA Systems for this sage piece of advice...

2013 Business Process Flows can contain fields as part of the definition.

By default, these are unlocked which may not be what you intend, especially if you want to maintain required fields by workflow or other process.

For example, you have a flag maintained by process 'All actions complete'. The Business Process Flow definition does not allow you to set the field read only or locked.

The solution is to use a Business Rule to lock the field. This will fire and will set the attribute of the field in the Business Process Flow ribbon, regardless of whether the field is elsewhere on the form.

You can also use a Business Rule to set the field value.

A Hidden Rule Around Business Rules in CRM 2013

This is a re-post from a colleague - credit must go to Martin Miller at OA Systems for this sage piece of advice...

2013 business rules fail to work when you reference a field not present on the form.

This is a gotcha if you are relying on business rules with a wide scope across multiple forms.

Not only does the business rule not complete, it does not do anything and in fact does not even throw an error.

So you cannot hope that some of your business rule will work if you reference a field not on form, none of it works, it is as though it never happened.

Tuesday, January 28, 2014

Loading Dependent Web Resource Scripts for an Application Ribbon Button

(from Simon Kendall - learned CRM colleague an architect)

Let’s say I have  button on the homepage of an entity in CRM and it makes a REST service call. 

function onProgressWindowLoaded() {
    [code removed for clarity]
       XrmServiceToolkit.Rest.Retrieve(
            ticketIds[i],
            "apitil_incidentSet",
            null, null,
            function(result) {
               
            },
            function() { //can have error parameter i.e. error.message
                errorNotes += "Skipped: Ticket Id " + ticketReferences[i].Name + " error retrieving this ticket.";
            },
            false
        );

How do I load the web resource with the XrmServiceToolkit methods in it?  Multiple command actions using a dummy function name are your friend.  There are one million examples of this on the interweb.  It looks something like this in Ribbon Workbench:
Adding a button from the Ribbon Workbench


Registering Workflow Assemblies to Disk in CRM 2011

So I'm registering a nice, new workflow assembly to Disk in CRM 2011 (so I can debug a workflow for a colleague).
Having loaded the assembly itself, the CRM Plugin Registration Tool give me the following (unhelpful) error:

Assembly can not be loaded. (Error Code -2147200995)

Now there isn't much help on the Interwebs on this error number, so I had to resort to the CRM Trace Log. The trace log was equally unhelpful (I've included it here for reference):

[2014-01-29 09:46:16.411] Process: w3wp |Organization:782510f2-4d72-e211-bd58-001e6837aeb7 |Thread:   25 |Category: Platform.Sql |User: 85e569d9-f7d6-e211-8851-00155d0a8940 |Level: Info |ReqId: 11a1058c-12c8-4ef4-9007-ea5bf87fba64 | BusinessProcessObject.ExecuteQuery  ilOffset = 0x3E
>select 
"plugintype0".TypeName as "typename"
, "plugintype0".IsWorkflowActivity as "isworkflowactivity"
, "plugintype0".PluginTypeId as "plugintypeid" 
from
 PluginType as "plugintype0" 
where
 (("plugintype0".PluginAssemblyId = 'f3c498c4-f911-4a9a-a3da-c741be957d08'))
[2014-01-29 09:46:16.411] Process: w3wp |Organization:782510f2-4d72-e211-bd58-001e6837aeb7 |Thread:   25 |Category: Platform.Sql |User: 85e569d9-f7d6-e211-8851-00155d0a8940 |Level: Verbose |ReqId: 11a1058c-12c8-4ef4-9007-ea5bf87fba64 | CrmDbConnection.InternalExecuteReader  ilOffset = 0x16
>select 
"plugintype0".TypeName as "typename"
, "plugintype0".IsWorkflowActivity as "isworkflowactivity"
, "plugintype0".PluginTypeId as "plugintypeid" 
from
 PluginType as "plugintype0" 
where
 (("plugintype0".PluginAssemblyId = 'f3c498c4-f911-4a9a-a3da-c741be957d08'))
[2014-01-29 09:46:16.411] Process: w3wp |Organization:782510f2-4d72-e211-bd58-001e6837aeb7 |Thread:   25 |Category: Platform.Sql |User: 85e569d9-f7d6-e211-8851-00155d0a8940 |Level: Verbose |ReqId: 11a1058c-12c8-4ef4-9007-ea5bf87fba64 | CrmDbConnection.InternalExecuteReader  ilOffset = 0xDD
>Query execution time: 0.0 seconds; database: CRMORG_MSCRM; Server:SQLSERVER; command: select 
"plugintype0".TypeName as "typename"
, "plugintype0".IsWorkflowActivity as "isworkflowactivity"
, "plugintype0".PluginTypeId as "plugintypeid" 
from
 PluginType as "plugintype0" 
where
 (("plugintype0".PluginAssemblyId = 'f3c498c4-f911-4a9a-a3da-c741be957d08')).
[2014-01-29 09:46:16.489] Process: w3wp |Organization:782510f2-4d72-e211-bd58-001e6837aeb7 |Thread:   25 |Category: Exception |User: 85e569d9-f7d6-e211-8851-00155d0a8940 |Level: Error |ReqId: 11a1058c-12c8-4ef4-9007-ea5bf87fba64 | CrmException..ctor  ilOffset = 0x0
 at CrmException..ctor(String message, Exception innerException, Int32 errorCode, Boolean isFlowControlException)  ilOffset = 0x0
 at CrmException..ctor(String message, Int32 errorCode)  ilOffset = 0x0
 at CustomActivityInfoMetadataBase.GetCustomActivityInfo(ActivityInfo activityInfo, Int32 isolationMode)  ilOffset = 0x17A
 at WorkflowSandboxCustomActivityValidator.ValidateInternal()  ilOffset = 0x1EF
 at PluginValidatorBase.Validate()  ilOffset = 0x12
 at PluginTypeValidator.ValidateInternal()  ilOffset = 0xCE
 at PluginValidatorBase.Validate()  ilOffset = 0x12
 at AssemblyDataCanBeUpdatedValidator.ValidateInternal()  ilOffset = 0x261
 at PluginAssemblyServiceInternal`1.VerifyRegistrationAbility(IBusinessEntity pluginAssembly, Boolean createCall, ExecutionContext context)  ilOffset = 0x8E
 at SdkEntityServiceBase.UpdateInternal(IBusinessEntity entity, ExecutionContext context, Boolean verifyAction)  ilOffset = 0x10
 at PluginAssemblyServiceInternal`1.Update(IBusinessEntity entity, ExecutionContext context)  ilOffset = 0x4A
 at RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)  ilOffset = 0xFFFFFFFF
 at RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)  ilOffset = 0x25
 at RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)  ilOffset = 0x84
 at LogicalMethodInfo.Invoke(Object target, Object[] values)  ilOffset = 0x4F
 at InternalOperationPlugin.Execute(IServiceProvider serviceProvider)  ilOffset = 0x57
 at V5PluginProxyStep.ExecuteInternal(PipelineExecutionContext context)  ilOffset = 0x58
 at VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)  ilOffset = 0x65
 at Pipeline.Execute(PipelineExecutionContext context)  ilOffset = 0x65
 at MessageProcessor.Execute(PipelineExecutionContext context)  ilOffset = 0x1C5
 at InternalMessageDispatcher.Execute(PipelineExecutionContext context)  ilOffset = 0xE4
 at ExternalMessageDispatcher.ExecuteInternal(IInProcessOrganizationServiceFactory serviceFactory, IPlatformMessageDispatcherFactory dispatcherFactory, String messageName, String requestName, Int32 primaryObjectTypeCode, Int32 secondaryObjectTypeCode, ParameterCollection fields, CorrelationToken correlationToken, CallerOriginToken originToken, UserAuth userAuth, Guid callerId, Guid transactionContextId, Int32 invocationSource, Nullable`1 requestId, Version endpointVersion)  ilOffset = 0x156
 at OrganizationSdkServiceInternal.ExecuteRequest(OrganizationRequest request, CorrelationToken correlationToken, CallerOriginToken callerOriginToken, WebServiceType serviceType, UserAuth userAuth, Guid targetUserId, Boolean traceRequest, OrganizationContext context, Boolean returnResponse)  ilOffset = 0x145
 at OrganizationSdkServiceInternal.ExecuteRequest(OrganizationRequest request, CorrelationToken correlationToken, CallerOriginToken callerOriginToken, WebServiceType serviceType)  ilOffset = 0x34
 at OrganizationSdkServiceInternal.Update(Entity entity, CorrelationToken correlationToken, CallerOriginToken callerOriginToken, WebServiceType serviceType)  ilOffset = 0x2D
 at   ilOffset = 0xFFFFFFFF
 at SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)  ilOffset = 0x241
 at DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)  ilOffset = 0x100
 at ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)  ilOffset = 0x48
 at ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)  ilOffset = 0xC6
 at MessageRpc.Process(Boolean isOperationContextSet)  ilOffset = 0x62
 at ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)  ilOffset = 0x256
 at ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)  ilOffset = 0xF1
 at ChannelHandler.AsyncMessagePump(IAsyncResult result)  ilOffset = 0x39
 at AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)  ilOffset = 0x0
 at AsyncResult.Complete(Boolean completedSynchronously)  ilOffset = 0xC2
 at ReceiveItemAndVerifySecurityAsyncResult`2.InnerTryReceiveCompletedCallback(IAsyncResult result)  ilOffset = 0x55
 at AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)  ilOffset = 0x0
 at AsyncResult.Complete(Boolean completedSynchronously)  ilOffset = 0xC2
 at AsyncQueueReader.Set(Item item)  ilOffset = 0x21
 at InputQueue`1.Dispatch()  ilOffset = 0x121
 at ScheduledOverlapped.IOCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)  ilOffset = 0x22
 at IOCompletionThunk.UnhandledExceptionFrame(UInt32 error, UInt32 bytesRead, NativeOverlapped* nativeOverlapped)  ilOffset = 0x5
 at _IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)  ilOffset = 0x3C
>Crm Exception: Message: Assembly can not be loaded., ErrorCode: -2147200995
[2014-01-29 09:46:16.489] Process: w3wp |Organization:00000000-0000-0000-0000-000000000000 |Thread:   25 |Category: Platform |User: 85e569d9-f7d6-e211-8851-00155d0a8940 |Level: Info |ReqId: 11a1058c-12c8-4ef4-9007-ea5bf87fba64 | ExceptionConverter.IsSandboxException  ilOffset = 0x3F
>IsSandboxException: TargetInvocationException
[2014-01-29 09:46:16.489] Process: w3wp |Organization:00000000-0000-0000-0000-000000000000 |Thread:   25 |Category: Platform |User: 85e569d9-f7d6-e211-8851-00155d0a8940 |Level: Info |ReqId: 11a1058c-12c8-4ef4-9007-ea5bf87fba64 | ExceptionConverter.IsSandboxException  ilOffset = 0x8B
>IsSandboxException: false
[2014-01-29 09:46:16.505] Process: w3wp |Organization:782510f2-4d72-e211-bd58-001e6837aeb7 |Thread:   25 |Category: Platform.Sdk |User: 85e569d9-f7d6-e211-8851-00155d0a8940 |Level: Error |ReqId: 11a1058c-12c8-4ef4-9007-ea5bf87fba64 | VersionedPluginProxyStepBase.Execute  ilOffset = 0x65
>Web Service Plug-in failed in SdkMessageProcessingStepId: {A0CDBB1B-EA3E-DB11-86A7-000A3A5473E8}; EntityName: pluginassembly; Stage: 30; MessageName: Update; AssemblyName: Microsoft.Crm.Extensibility.InternalOperationPlugin, Microsoft.Crm.ObjectModel, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35; ClassName: Microsoft.Crm.Extensibility.InternalOperationPlugin; Exception: Unhandled Exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Web.Services.Protocols.LogicalMethodInfo.Invoke(Object target, Object[] values)
   at Microsoft.Crm.Extensibility.InternalOperationPlugin.Execute(IServiceProvider serviceProvider)
   at Microsoft.Crm.Extensibility.V5PluginProxyStep.ExecuteInternal(PipelineExecutionContext context)
   at Microsoft.Crm.Extensibility.VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)
Inner Exception: Microsoft.Crm.CrmException: Assembly can not be loaded.
   at Microsoft.Crm.Workflow.ObjectModel.CustomActivityInfoMetadataBase.GetCustomActivityInfo(ActivityInfo activityInfo, Int32 isolationMode)
   at Microsoft.Crm.ObjectModel.WorkflowSandboxCustomActivityValidator.ValidateInternal()
   at Microsoft.Crm.ObjectModel.PluginValidatorBase.Validate()
   at Microsoft.Crm.ObjectModel.PluginTypeValidator.ValidateInternal()
   at Microsoft.Crm.ObjectModel.PluginValidatorBase.Validate()
   at Microsoft.Crm.ObjectModel.AssemblyDataCanBeUpdatedValidator.ValidateInternal()
   at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.VerifyRegistrationAbility(IBusinessEntity pluginAssembly, Boolean createCall, ExecutionContext context)
   at Microsoft.Crm.ObjectModel.SdkEntityServiceBase.UpdateInternal(IBusinessEntity entity, ExecutionContext context, Boolean verifyAction)
   at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.Update(IBusinessEntity entity, ExecutionContext context)
.
[2014-01-29 09:46:16.505] Process: w3wp |Organization:782510f2-4d72-e211-bd58-001e6837aeb7 |Thread:   25 |Category: Exception |User: 85e569d9-f7d6-e211-8851-00155d0a8940 |Level: Error |ReqId: 11a1058c-12c8-4ef4-9007-ea5bf87fba64 | CrmException..ctor  ilOffset = 0x0
 at CrmException..ctor(String message, Exception innerException, Int32 errorCode, Boolean isFlowControlException)  ilOffset = 0x0
 at CrmException..ctor(String message, Exception innerException, Int32 errorCode)  ilOffset = 0x0
 at RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)  ilOffset = 0xFFFFFFFF
 at RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)  ilOffset = 0xF7
 at RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark)  ilOffset = 0x1E8
 at Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)  ilOffset = 0xB1
 at Activator.CreateInstance(Type type, Object[] args)  ilOffset = 0x0
 at VersionedPluginProxyStepBase.WrapExceptionToThrow(CrmException exception)  ilOffset = 0xA5
 at VersionedPluginProxyStepBase.Execute(PipelineExecutionContext context)  ilOffset = 0x65
 at Pipeline.Execute(PipelineExecutionContext context)  ilOffset = 0x65
 at MessageProcessor.Execute(PipelineExecutionContext context)  ilOffset = 0x1C5
 at InternalMessageDispatcher.Execute(PipelineExecutionContext context)  ilOffset = 0xE4
 at ExternalMessageDispatcher.ExecuteInternal(IInProcessOrganizationServiceFactory serviceFactory, IPlatformMessageDispatcherFactory dispatcherFactory, String messageName, String requestName, Int32 primaryObjectTypeCode, Int32 secondaryObjectTypeCode, ParameterCollection fields, CorrelationToken correlationToken, CallerOriginToken originToken, UserAuth userAuth, Guid callerId, Guid transactionContextId, Int32 invocationSource, Nullable`1 requestId, Version endpointVersion)  ilOffset = 0x156
 at OrganizationSdkServiceInternal.ExecuteRequest(OrganizationRequest request, CorrelationToken correlationToken, CallerOriginToken callerOriginToken, WebServiceType serviceType, UserAuth userAuth, Guid targetUserId, Boolean traceRequest, OrganizationContext context, Boolean returnResponse)  ilOffset = 0x145
 at OrganizationSdkServiceInternal.ExecuteRequest(OrganizationRequest request, CorrelationToken correlationToken, CallerOriginToken callerOriginToken, WebServiceType serviceType)  ilOffset = 0x34
 at OrganizationSdkServiceInternal.Update(Entity entity, CorrelationToken correlationToken, CallerOriginToken callerOriginToken, WebServiceType serviceType)  ilOffset = 0x2D
 at   ilOffset = 0xFFFFFFFF
 at SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)  ilOffset = 0x241
 at DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)  ilOffset = 0x100
 at ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)  ilOffset = 0x48
 at ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)  ilOffset = 0xC6
 at MessageRpc.Process(Boolean isOperationContextSet)  ilOffset = 0x62
 at ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)  ilOffset = 0x256
 at ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)  ilOffset = 0xF1
 at ChannelHandler.AsyncMessagePump(IAsyncResult result)  ilOffset = 0x39
 at AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)  ilOffset = 0x0
 at AsyncResult.Complete(Boolean completedSynchronously)  ilOffset = 0xC2
 at ReceiveItemAndVerifySecurityAsyncResult`2.InnerTryReceiveCompletedCallback(IAsyncResult result)  ilOffset = 0x55
 at AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)  ilOffset = 0x0
 at AsyncResult.Complete(Boolean completedSynchronously)  ilOffset = 0xC2
 at AsyncQueueReader.Set(Item item)  ilOffset = 0x21
 at InputQueue`1.Dispatch()  ilOffset = 0x121
 at ScheduledOverlapped.IOCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)  ilOffset = 0x22
 at IOCompletionThunk.UnhandledExceptionFrame(UInt32 error, UInt32 bytesRead, NativeOverlapped* nativeOverlapped)  ilOffset = 0x5
 at _IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)  ilOffset = 0x3C
>Crm Exception: Message: Assembly can not be loaded., ErrorCode: -2147200995, InnerException: Microsoft.Crm.CrmException: Assembly can not be loaded.
   at Microsoft.Crm.Workflow.ObjectModel.CustomActivityInfoMetadataBase.GetCustomActivityInfo(ActivityInfo activityInfo, Int32 isolationMode)
   at Microsoft.Crm.ObjectModel.WorkflowSandboxCustomActivityValidator.ValidateInternal()
   at Microsoft.Crm.ObjectModel.PluginValidatorBase.Validate()
   at Microsoft.Crm.ObjectModel.PluginTypeValidator.ValidateInternal()
   at Microsoft.Crm.ObjectModel.PluginValidatorBase.Validate()
   at Microsoft.Crm.ObjectModel.AssemblyDataCanBeUpdatedValidator.ValidateInternal()
   at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.VerifyRegistrationAbility(IBusinessEntity pluginAssembly, Boolean createCall, ExecutionContext context)
   at Microsoft.Crm.ObjectModel.SdkEntityServiceBase.UpdateInternal(IBusinessEntity entity, ExecutionContext context, Boolean verifyAction)
   at Microsoft.Crm.ObjectModel.PluginAssemblyServiceInternal`1.Update(IBusinessEntity entity, ExecutionContext context)

So on the surface of it not entirely helpful, particularly when the assembly registers without issue to the database, just not to disk. All our plugin assemblies register correctly, just not our workflow assemblies.
Our combined thinking was that, perhaps, an older assembly reference was unable to be loaded. So here's what we did:
  1. Go through all the assemblies that referenced our workflow assembly (our Data, CRM Repository, and Business Logic are all abstracted away to other assemblies for later - more on this at another time) and make sure that these assemblies don't reference anything that they don't need to.
    • We found an unused reference to Microsoft.Xrm.Client.dll that wasn't required in the CRM Repository assembly, so we removed that.
  2. Removed all references in %Program Files%\Microsoft Dynamics CRM\Server\bin\assembly other than the assemblies we were registering to ensure that the .
  3. Go through all the projects that referenced CRM assemblies, and ensure that they were referencing the same identical XRM assemblies, from the same location.. We were surprised how many projects and little plugins we had created from the XRM project wizard referenced XRM assemblies from a different location.
We then rebuilt and re-registered the offending assembly, and, hey presto! It worked. So a combination of the above improvements in best-practise (which seem obvious now, but require eternal vigilance) and keeping your dev house tidy, worked a treat.
As much as anything, it was another lesson in not just having clean code, but a clean implementation and a clean deployment too.
Now, if I can just convince all my clients onto the same CRM rollup, I'll be laughing!
Happy deploying everyone!

Monday, January 6, 2014

SQL Timeouts when Importing CRM 4.0 Customisations

Another unhelpful aspect of importing CRM 4.0 customisations is that a SQL timout can occur when importing or creating a new entity. I believe (from checking the SQL Server Profiler when this is happening) is that CRM triggers re-creation of indexes in CRM using the

exec p_RecreateIndexes

stored procedure.

On large systems this can exceed the default timeout, or even a custom timeout if you've added one. I got around this issue by running the stored procedure manually before importing the customisations to minimise the amount of re-indexing that took place.

You can also increase the timeout in CRM:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSCRM\OLEDBTimeout

This value does not exist by default, and if it does not exist then the default value will be 30 seconds. To change it, add the registry value (of type DWORD), and put in the value you want (measured in seconds). Then you have to recycle the CrmAppPool application pool for the change to take effect

https://community.dynamics.com/product/crm/crmtechnical/b/crmdavidjennaway/archive/2008/09/04/sql-timeouts-in-crm-generic-sql-error.aspx

Missing Attribute Labels when Importing CRM 4.0 Customisations

So here I am preparing a release for our QA team to test. It's a pretty simple release, just a few entities to go into a big QA system for load testing. Imagine my surprise when 4 of the 5 entities import, and one consistently throws up unhelpful CRM errors. Here's what I can find in the Event Log on the CRM server:

"Customization Import failed. Error: Attribute Display Name not specified"

To cut a long story short - I had several attributes on my entity that were unpublished - a rookie mistake! So make sure you publish your entity before exporting it. Why this happens, I don't know - all I know at this point was that before re-publishing the entity customisations my attribute looked like this:

<attribute PhysicalName="my_attributename">
    <Type>datetime</Type>
    <ValidForCreateApi>1</ValidForCreateApi>
    <ValidForUpdateApi>1</ValidForUpdateApi>
    <ValidForReadApi>1</ValidForReadApi>
    <IsCustomField>1</IsCustomField>
    <AttributeTypeId>00000000-0000-0000-00aa-110000000015</AttributeTypeId>
    <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask>
    <ImeMode>auto</ImeMode>
    <RequiredLevel>none</RequiredLevel>
    <Format>date</Format>
</attribute>

After publishing becomes:

<attribute PhysicalName="my_attributename">
    <Type>datetime</Type>
    <ValidForCreateApi>1</ValidForCreateApi>
    <ValidForUpdateApi>1</ValidForUpdateApi>
    <ValidForReadApi>1</ValidForReadApi>
    <IsCustomField>1</IsCustomField>
    <AttributeTypeId>00000000-0000-0000-00aa-110000000015</AttributeTypeId>
    <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask>
    <Descriptions>
        <Description description="Attribute Name" languagecode="1033" />
    </Descriptions>
    <ImeMode>auto</ImeMode>
    <RequiredLevel>none</RequiredLevel>
    <Format>date</Format>
    <displaynames>
      <displayname description="Attribute Name" languagecode="1033" />
    </displaynames>
</attribute>

Why am I here?

So why blog about CRM?  I mean, it's not like there aren't thousands of better qualified CRM consultants out there, right?

Well - I forget a lot of stuff. There's not a lot of room in my head left once I fill it full of phone numbers, shopping lists, useless trivia and sports statistics. So either I write down all the things I need to remember for work, or forget Michael Jordan's NBA plethora of awards, or Don Bradman's batting average.

So - here it is.  All the little gotchas that I run into every day and drive me up the wall.  I'm constantly amazed by how many times I see or send 'useful' emails regarding Dynamics CRM, or development generally that I think - "yeah, I'll remember that - it's quite useful" and then forget it immediately. But no longer!

I hope there'll be something other than CRM on here one day our company does much more than CRM but I find all the things that drive me nuts are CRM related. I wonder why that is?

I hope this is useful for maybe one person other than me.  If it is, then I've done my job.  Well I probably haven't because I've been blogging on work time.  But that's professional development, right?  Surely. I wonder how I put this on my timesheet...