Thursday, 22 May 2008

Blog Update - Links to Code Gallery Resources

I've added an extra section to this blog. This is a list of all my published resources on the MSDN Code Gallery, along with a link to all other CRM-related resources there. At the time of posting, there were 66 resources in the code gallery with a CRM tag.

Posting on Microsoft Dynamics CRM Team Blog

I've been invited to post again on the Microsoft Dynamics CRM Team Blog. This time I've included information about plugin messages, and how a couple of tools can be used to record information about all plugin events on a CRM 4.0 deployment. The article is here: http://blogs.msdn.com/crm/archive/2008/05/21/plugins-which-message-and-which-pipeline.aspx

The two tools are both available on the MSDN Code Gallery:
http://code.msdn.microsoft.com/crm40pluginbulk
http://code.msdn.microsoft.com/crm40pluginlogger

Wednesday, 21 May 2008

Some Plugin Tools

I've posted 2 resources (.Net projects) to the MSDN Code Gallery to assist with writing and deploying plugins for CRM 4.0.

One tool is a logging tool This writes details about any plugin event to SQL tables. This records standard event data - date, message, primary entity, stage and pipeline - and also any data passed in property bags - InputParameter, OutputParameter, PreEntityImage, PostEntityImage and SharedVariable. I find this very useful when determining what information is passed for each message.

The other tool is a registration tool. This is a set of enhancements to the plugindeveloper that is included in the CRM 4.0.4 SDK. The main enhancement is to allow registration of a plugin against all messages or all entities. I use this in conjunction with the logging tool to allow me to quickly build an environment that captures all customisable plugin events on a CRM deployment. This tool makes use of the sdkmessagefilter class to determine which combinations of plugin event and message are available on a CRM deployment

Both resources on the Code Gallery contain the full source code, which is made available under the Microsoft Public License (Ms-PL), as well as installation instructions

More information about these tools, and an explanation of the code will shortly be posted on the Microsoft Dynamics CRM Team Blog. Rather than repeat the content here, I'll post a link when it's live.

Updated 21-05-08: The Team Blog link is http://blogs.msdn.com/crm/archive/2008/05/21/plugins-which-message-and-which-pipeline.aspx

Monday, 21 April 2008

CRM 4.0 Upgrade error. An item with the same key has already been added

Recently when testing a CRM 4.0 upgrade for a customer, I got the error 'An item with the same key has already been added'. I'm not the first to have met this problem (see the MS newsgroup), and probably won't be the last).

From what I've found so far there are 2 possible causes, both to do with CRM believing there are duplicate field names. Either a duplication has occured between a custom field, and a virtual field, or you have somehow got a duplicate entry in FieldXml.

Duplication between a custom field, and a virtual field
For certain field types (boolean, picklist and lookup), CRM creates an additional attribute in the metadata for the text of the field. This attrbiute is not physically stored, and is considered to be a 'virtual' field. The virtaul attribute has the name of the base field + a suffix of 'name'. For example, the prioritycode picklist field results in a corresponding prioritycodename virtual field, and a custom lookup field new_accountid would result in a virtual field new_accountidname (there is also a new_accountiddsc virtual).

This can cause a problem if you created an additional, physical field with the same name as a virtual field. CRM will let you do this, but the upgrade could fail.

To determine if this is a potential problem, run the following SQL script in the METABASE SQL database. This will return a list of any duplicates (you'll need dbo rights to run the script)

select e.name as entity, a.name as attribute, count(*)
from attribute a join entity e on a.entityid = e.entityid
group by e.name, a.name
having count(*) > 1

If you've found duplicates, your only solution is to remove the duplicate field from CRM.

Duplicate entry in FieldXml
CRM stores metadata in several places in CRM. One of them is the OrganizationUIBase table in the MSCRM database. Within this table, some data is stored in the FieldXml field. This is an XML document that looks something like this:

<entity name="prism_utilisationperiod" objecttypecode="10003">
<fields>
<field name="prism_utilisationperiodid" requiredlevel="na">
<displaynames>
<displayname description="UtilisationPeriod" languagecode="1033" />
</displaynames>
</field>
<field name="createdon" requiredlevel="na" format="date">
<displaynames>
<displayname description="Created On" languagecode="1033" />
</displaynames>
</field>
</fields>
</entity>

There should only be one field element for each CRM attribute, but the problem I had was that a field element was duplicated.

To determine any duplicate fields, use the following SQL in the MSCRM database:

declare @fieldXml nvarchar(max), @otc int, @idoc int
declare curEntity cursor fast_forward for
select ObjectTypeCode, FieldXml from OrganizationUIBase where InProduction = 1
open curEntity
fetch next from curEntity into @otc, @fieldXml
while @@fetch_status = 0
begin
EXEC sp_xml_preparedocument @idoc OUTPUT, @fieldXml
if exists ( select name as field from OpenXML(@idoc, '/entity/fields/field') WITH (name nvarchar(64)) group by name having count(*) > 1 )
select @otc as otc, name as field, count(*) as occurences
from OpenXML(@idoc, '/entity/fields/field') WITH (name nvarchar(64))
group by name
having count(*) > 1
EXEC sp_xml_removedocument @idoc
fetch next from curEntity into @otc, @fieldXml
end
close curEntity
deallocate curEntity

This will list the objecttypecode and attribute name of any duplicates. If you have duplicates, the resolution is to export the customisations for the relevant entities, delete any duplicate field elements from the customisation xml, then reimport the xml and publish the entities.

Credit is due to Michael Hohne and Kesh Patel who've contributed to finding the problems and solutions.

Thursday, 3 April 2008

Email Templates with Custom Entities

I've recently been doing some development work creating emails based on templates. In most cases this is straightforward, once you've applied CRM 3.0 Update Rollup 1, 2 or 3, and also updated any web references. However, there are a couple of quirks I found when trying to use custom entities.

First; you can only use Global templates with custom entities. CRM 3.0 does not allow you to create an entity-specific template. This led to the first quirk; I was using a QueryExpression to find a specific template by name, and also templatetypecode (as CRM allows you to create templates with the same name but on different entities). When searching for a Global template, the templatetypecode is systemuser, rather than null as I was expecting.

Next, when using InstantiateTemplateRequest, it failed if the ObjectType were set to any custom entity. The error message is the unhelpful 'An unexpected error occurred'. Again, the solution is to treat a Global template as if it were actually a template for the systemuser entity. Therefore to make it work you have to specify an ObjectId and ObjectType for a systemuser - it doesn't seem to matter which systemuserid you use.

Putting this together, the following code shows how to create an email based on a template for a custom entity:


public email CreateEmailFromGlobalTemplate(string TemplateName, string EntityTypeName, Guid ObjectId, Guid SomeUserId)
{
QueryByAttribute qa = new QueryByAttribute();
qa.EntityName = EntityName.template.ToString();
qa.Attributes = new string[] {"title", "templatetypecode"};
qa.Values = new object[] {TemplateName, EntityName.systemuser.ToString()}; // Use systemuser for global template
ColumnSet cs = new ColumnSet();
cs.Attributes = new string[]{"templateid"};
qa.ColumnSet = cs;
BusinessEntityCollection bec = svc.RetrieveMultiple(qa);
if (bec == null && bec.BusinessEntities.Length == 0)
throw new Exception("Cannot find template");
Guid templateId = ((template) bec.BusinessEntities[0]).templateid.Value;
InstantiateTemplateRequest req = new InstantiateTemplateRequest();
req.ObjectId = SomeUserId; // Any valid systemuserid
req.ObjectType = EntityName.systemuser.ToString(); // Use systemuser for global template
req.TemplateId = templateId;
InstantiateTemplateResponse resp = (InstantiateTemplateResponse) svc.Execute(req);
if (resp == null && resp.BusinessEntityCollection.BusinessEntities.Length == 0)
throw new Exception("Cannot create email");
email newMail = (email) resp.BusinessEntityCollection.BusinessEntities[0];
newMail.regardingobjectid = new Lookup();
newMail.regardingobjectid.type = EntityTypeName;
newMail.regardingobjectid.Value = ObjectId;
return newMail;
}


One more thing to bear in mind: If you use InstantiateTemplateRequest then the code has to run under a CRM context of a live user, rather than the SYSTEM user, otherwise you will get the error 'The specified users settings have not yet been created' - I guess because CRM also looks at user templates

Monday, 3 March 2008

Workflow assembly. Trying to make a Lookup parameter optional

When creating a workflow assembly for CRM 3.0, CRM has no proper understanding of optional parameters. The best way to cope with this is to specify a default value in the workflow.config file, but I found a weird issue with parameters of type lookup.

What I expected to able to do was specify a default of {00000000-0000-0000-0000-000000000000} which would map to Guid.Empty. Creating a rule in Workflow Manager was fine, but when it came the running the rule, it failed prior to calling the assembly with the following error in Workflow Monitor:

'Error code = 800040005. The Microsoft CRM server is unavailable. There is a problem communicating with the Microsoft CRM server.' This is obviously an incorrect error message, given the message itself was written to the CRM server. Further investigation showed the problem was with the above Guid, and anything other than an empty Guid works fine, so I now use {FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}

Wednesday, 30 January 2008

MSDN Code Gallery

Microsoft have just made the new MSDN Code Gallery live. This is a site for sharing sample code, and I've posted some of my example code from previous blog articles. So far I've posted the following 4 resources:

MSCRM: Automate Local Data Group creation

Microsoft CRM report of all files related to an account

MSCRM - web page hosting to extend client script

Writing reports to use in IFrames and from ISV.Config in Microsoft Dynamics CRM

For reference, if you want to search for CRM examples, there's an emerging tagging protocol to tag all CRM examples as both CRM and Dynamics, so you can search on either tag. I'm also tagging samples with reports as 'Reporting Services'

Saturday, 26 January 2008

Scheduling and emailing reports with CRM

A common customer request is to be able to schedule CRM reports and automatically send the report to one or more users by email. This can be done without the need for any code, but needs a combination of configuration within both CRM and Reporting Services.

CRM Configuration
CRM reports are designed to be run under the context of the user running the report so as to return only the data that user has permission to see. Due to this configuration, Reporting Services will not permit you to schedule CRM Reports out of the box (if you try, you'll get the error 'Subscriptions cannot be created because the credentials used to run the report are not stored') . The easiest way to resolve this issue is to download the CRM Report Scheduling Wizard from here and install it on the CRM server.

Then, navigate to Workplace, Reports in CRM, select the report you want to schedule, and go to More Actions, Schedule Report. As you will do the scheduling later within Report Manager, I'd suggest you select the following options:
  • Generate snapshots 'On demand'
  • 'Make snapshots available only to me'
  • Specify any parameter values
  • You then need to specify the credentials under which the report will run. This needs to be a valid CRM user
  • 'Yes, generate the snapshot now'

Reporting Services configuration
The rest of the configuration is done via Reporting Services. First of all, you may have to configure the email properties of Reporting Services. To do this open RSReportServer.config, which by default will be in the Reporting Services\ReportServer directory under the SQL installation directory in Program Files. Within the 'RSEmailDPConfiguration' element you will need to set values in, as a minimum, the 'SMTPServer' and 'From' elements. You may need to apply other settings. For more information see the documentation for SQL 2005 or SQL 2000

Scheduling the Report in Report Manager
You can now schedule the report. Browse to Report Manager (by default it will be in the Reports virtual directory), go to the OrganisationName_MSCRM folder and the report you have scheduled (it's name will have the suffix On demand Snapshot). On the toolbar you should have a button 'New Subscription'. Click this, select 'Report Server E-mail' in the Delivered by drop down, and set all other options as required. (If 'Report Server E-mail' is not an option, go back to the instructions in the above paragraph

Monday, 14 January 2008

CRM 4.0 Error with ISV pages: MultipleOrganizationSoapHeaderAuthenticationProvider

If you have a CRM 3.0 Server with custom ASP.Net pages in a virtual directory within the CRM website, when you upgrade the server to CRM 4.0, the custom pages fail, giving the error:

Microsoft.Crm.WebServices.Crm2007.MultipleOrganizationSoapHeaderAuthenticationProvider, Microsoft.Crm.WebServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' doesn't exist

You can also get the same error when creating a new virtual directory within the CRM website.

The reason for the error is that CRM adds 2 HttpModules (MapOrg and CrmAuthentication) to web.config in the root of the CRM website. The CrmAuthentication HttpModule is in the microsoft.crm.webservices.dll assembly, which is not in the path of the custom virtual directory.
There are two solutions to this. Either:
  1. Ensure microsoft.crm.webservices.dll can be found. To do this, add it to the global assembly cache, then run iisreset (or recycle the CrmAppPool application pool)
  2. Remove the CrmAuthentication HttpModule from the virtual directory. I haven't fully investigated whether this adversely impacts use of the CRM 4.0 web services endpoint, but can be safely done if the ASP >net pages use the CRM 3.0 web services endpoint (or don't access CRM web services at all). To remove the HttpModule, add the following to the web.config in the custom virtual directory (within the system.web element):

<httpModules>
<remove name ="CrmAuthentication" />
</httpModules>

Thursday, 20 December 2007

CRM 4.0 - Installation with no Internet Access

The current set of downloadable files for CRM 4.0 do not include some pre-requisite software (including .Net 3.0 framework and Visual C++ redistributable). If this software is not already installed and the setup program can't find it locally, the CRM setup program can download the files from the Internet, which is fine, if you have Internet access from the CRM server...

If you don't have Internet access you can pre-install these components, except the Visual C++ redistributable which cannot be reliably detected, so the CRM setup program will always try to install it.

Therefore, without Internet access, you have to have a local copy of the Visual C++ redistributable locally. Philip Richardson's blog describes where to put these files, so I won't bother repeating it here, but there are a couple of extra things that it might help to know:
  1. CRM setup checks the version of the local copy of the redistributable, and if it's not what it expects, will again try to connect to the Internet. You can identify this scenario from the setup log; the message is "Error when checking signature", though oddly it's marked as an Info message
  2. CRM setup only checks for local files when it starts. So if, like me, you get to the screen where it expects to download the files, realise it won't be able to, then copy them locally, then go Back, Forward, it won't help. Instead, exit setup completely and start over again

As to Internet access from the CRM server, in most circumstances this can be achieved from a VPC image with the use of more than one network adapter. See my earlier post