Showing posts with label sdk. Show all posts
Showing posts with label sdk. Show all posts


Automatic provisioning of Organizations

Microsoft CRM does have a separate webservice for doing deployment related activities. There is a Deployment SDK especially for this webservice. You can download that here. One of the activities you can execute is automatic provisioning of new organizations. In this post I'll dive into the code required for this as well as some of the common issues while doing so.

Basically following the next steps should be enough:
- create an application
- add a web reference to: http:///mscrmservices/2007/crmdeploymentservice.asmx
- write the following code:


Organization org = new Organization();
org.UniqueName = txtName.Text;
org.FriendlyName = txtName.Text;
org.SqlServerName = ConfigurationManager.AppSettings["SqlServerName"];
org.SrsUrl = ConfigurationManager.AppSettings["SrsUrl"];
org.BaseCurrencyCode = ConfigurationManager.AppSettings["BaseCurrencyCode"];
org.BaseCurrencyName = ConfigurationManager.AppSettings["BaseCurrencyName"];
org.BaseCurrencySymbol = ConfigurationManager.AppSettings["BaseCurrencySymbol"];

CreateRequest request = new CreateRequest();
request.Entity = org;

CrmDeploymentService crmDeployService = new CrmDeploymentService();
crmDeployService.Credentials = System.Net.CredentialCache.DefaultCredentials;

try
{
crmDeployService.Execute(request);
}
catch (SoapException se)
{
lblError.Text = String.Format("Creation of Organization failed with error:<br>{0}<br>Detail:<br>{1}", se.Message, se.Detail.InnerText);
}
catch (Exception ex)
{
lblError.Text = String.Format("Creation of Organization failed with error:<br>{0}", ex.Message);
}

When you don't want to use the default credentials, then you would supply the username and password like this:

crmDeployService.Credentials = new System.Net.NetworkCredential("administrator", "pass@word1", "litwareinc");

In some cases this might lead to a 401 Unauthorized error message though. This has to do with kerberos. When you do force your application to use NTLM though, then it works. You can do this by using this code:

CredentialCache credential = new System.Net.CredentialCache();
NetworkCredential netCred = new NetworkCredential("administrator", "pass@word1", "litwareinc");
credential.Add(new Uri("http://localhost:5555"), "NTLM", netCred);
crmDeployService.Credentials = credential;

Some other errors you might run into with their respective answers.

0x8004b001 Create new Organization (Name=orgname, Id=10d59133-ae34-de11-a5a9-0003ffab19fc) failed with Exception: System.Security.SecurityException: Requested registry access is not allowed.

This error occurs because the identity of the application pool which is used by Dynamics CRM has no rights to update the records in the registry hyve HKLM\Software\Microsoft\MSCRM. By default this is set to "Network Service". To get rid of the error, open the registry editor (regedit), browse to the MSCRM hyve, rightclick on the hyve and choose "Permissions". You can then give the correct user rights for updating the values.

Most likely you will now run into this error message:

Server was unable to process request.
Detail:
0x8004b001 Create new Organization (Name=orgname,
Id=2015e8d7-b434-de11-a5a9-000b2924ac91) failed with Exception: System.NullReferenceException: Object reference not set to an instance of an
object.

This message does show up because you are working with the Network Service as the Identity for the Application Pool. You should change the identity of the CrmAppPool (see my other blog post). Then do an iisreset and you should get passed this error message.

Do you run into this error message?

Server was unable to process request.
Detail:
0x8004b001 Create new Organization (Name=orgname, Id=2015e8d7-b434-de11-a5a9-0003ffab19fc) failed with Exception: Microsoft.Crm.CrmException: Invalid user auth.

Then you most likely have forgot to do an iisreset. Try that or even a complete server reboot.

Now your provisioning should be starting, but it won't finish due to this error message:

The operation has timed out

Creating a organization can take quite some time. The timeout should be increased so that the creation of the organization can finish successfully before the timeout has expired. Adding this line of code would help:

crmDeployService.Timeout = 6000000;


That should solve your issues regarding the automatically provisioning of organizations for Dynamics CRM.

There could be ofcourse many other issues like out of memory exceptions, but that you can 'easily' solve by adding memory. If you find any other issues with the respective solutions to this topic, please add them to the comments!



Plugin registration tools for Visual Studio 2005

The newest update of the SDK, version 4.0.8, includes updated projects for the plugin registration tool as well as the plugin developer tool. While I usually am pleased with updates, this update could cause some issues for some people. The updated projects are build in Visual Studio 2008 whereas they used to be build in Visual Studio 2005. This means that you will not be able anymore to run the projects on machines running VS2005 including the demo VPC.

I've luckily backupped the previous SDK and uploaded both the Visual Studio projects so that you can download them if neccesary:

Plugin Registration VS2005 (zip)
Plugin Developer VS2005 (zip)

Of course you could remove the solution file, create a new solution and add the current project as well, but the downloads work just as good.

Good luck with building your plugins!



Read-only URL Addressable Form

The SDK does describe URL Addressable Forms, but one thing which I do miss on this page, is details around how to link to a form in readonly format. This is done by using the following URL:
http://server/orgname/_forms/readonly/readonly.aspx?objTypeCode=objectTypeCode.
Of course you need to change the server to servername, orgname to organization name and the objectTypeCode to the entity object type code. For instance 1 for account or 1024 for product.



Creating Environment independent solutions

When developing a solution for CRM, make sure that it runs on all different crm environments. Many implementations have their CRM installation on another port compared to the development environments. A way to handle this, is to look at the location of MS CRM as it is stored in the registery.


// Set default values
private const string CRM_REG_DIRECTORY = @"software\Microsoft\mscrm\";
private const string CRM_SERVICE_PATH = @"/2006/crmservice.asmx";

// Create CrmService instance
service = new CrmService();
service.Url = GetCRMWebServiceRoot() + CRM_SERVICE_PATH;

/// <summary>
/// Returns the recorded web location from the registry
/// </summary>
private static string GetCRMWebServiceRoot()
{
string result = null;
try
{
RegistryKey regKey = Registry.LocalMachine.OpenSubKey(CRM_REG_DIRECTORY, false);
if (regKey != null)
{
result = (string) regKey.GetValue("ServerUrl");
}
else
{
throw new ApplicationException("Microsoft CRM could not be found on this computer.");
}
}
catch (Exception err)
{
throw new ApplicationException("Cannot retrieve registry value for CRM web service. " + err.Message);
}
return result;
}


Thanks to Richard McCormick for providing the codes :)



Interesting issues with the CRMDateTime continuum.

This is a guest post by a friend and colleague of mine: Leon Krancher. He has been digging into an issue regarding CRMDateTime and integration. Make sure you read this before you start your own integration!

Well I guess I’d better start off with a little introduction. My name is Leon Krancher and I’m a colleague of Ronald at Avanade. As we both work with CRM we occasionally find time to get together, have a beer and share experiences. Recently I told him about a problem I encountered while trying to create a service that would synchronize my customers Data warehouse with CRM. Ronald thought it was interesting and asked me to write about it for his blog. So here we go.

Let’s start out with a more detailed description of what had to be created.

The customer’s data warehouse contained information about its customers. This information is updated frequently by the customer’s backoffice systems as well as a number connections to third party systems. Whenever the data changed a database trigger would update a datatime field inside the database to indicate the exact moment the customers data was changed.
Because the data warehouse data was also used in CRM I was tasked with creating an application that would update the data stored in CRM whenever the data warehouse was updated. Unfortunately I only had a very limited timeslot in which this application would have to run so I had to find a way to update only those few records that had changed. To solve this problem I decided to do the following:

Ø First I would add an attribute to the entity in CRM that would be used to store the DateTime value used to store the date and time on which the data was originally created in the data warehouse.

Ø Second I wrote a cross-database join that would join the data warehouse on the CRM database, compare both datetime values and return those rows for which the entity in CRM was outdated.

Ø The last step was to use the CRM webservice to update the CRM entities returned by the previous query[1].

Unfortunately whenever I tried to run my tool it would always update all records even though the data warehouse had not changed.
After some frantic debugging[2] I finally figured out what the true problem was.

When storing my data in CRM I used the following code taken from the CRM SDK.


providedContact.new_nawsyncdate.Value = syncDateAccordingToDatabase.ToUniversalTime().ToString("u");

This converts the syncDateAccordingToDatabase DateTime object to a string in the universal time format so it can be stored in CRM. Unfortunately the universal time format omits any milliseconds.

So whenever SQL Server would compare the dateTime stored in the data warehouse to the dateTime stored in CRM the data warehouse DateTime that did contain any milliseconds would be seen as ‘larger’, hence the continuous cycle of updates.

After figuring this out the problem was easily solved by adjusting the database query so it would always remove any milliseconds before making the comparison.

I hope this provides you guys with both a funny story and some help if you ever run into a similar problem.

-Leon

[1] I couldn’t update the CRM database directly because that would prevent the execution of a callout

[2] In my defense, the matters were complicated because the data warehouse and the CRM databases were set to use different time zones making the problem appear like a conversion problem



FetchXML into a DataSet

Once upon a time in the Netherlands there was this developer who was working on MSCRM for quite some time. He started to notice that he had to search again and again for the same questions. Luckily he heard about the term 'blogging' and so he started to post the most frequent questions on a blog. The goal was to be able to quickly retrieve the information that he had found earlier. The fact that other people also read his blog and save themselves time to figure everything out themselves is just an added value to the blog.

Now with this knowledge you will understand why I post this piece of code:


private DataSet FetchDataSet(string fetchXml) {
CrmService service = new CrmService();
service.Credentials = System.Net.CredentialCache.DefaultCredentials;
//service.UnsafeAuthenticatedConnectionSharing = true; //enable in secure migration programs

string strResult = service.Fetch(fetchXml);
DataSet ds = new DataSet();
System.IO.StringReader reader = new System.IO.StringReader(strResult);
ds.ReadXml(reader);
return ds;
}

I'm just typing this way to often and I prefer to use copy and paste.

Furthermore a link to a previous post on how to quickly create a fetchXML:
http://ronaldlemmen.blogspot.com/2006/11/using-advanced-find-for-fetchxml.html

And a link to another post on how to make sure that you do fetch all records and not only the first 5000:
http://ronaldlemmen.blogspot.com/2006/08/fetch-all-records.html



Set a lookup to NULL

Quite often I see this question in the newsgroup: How do I set a lookup to a NULL value?

Here is the answer for when you're working in server side code:


Entity.Lookup = new Lookup();
Entity.Lookup.IsNull = true;
Entity.Lookup.IsNullSpecified = true;


this also works for customers:


contact.parentcustomerid = new Customer();
contact.parentcustomerid.IsNull = true;
contact.parentcustomerid.IsNullSpecified = true;


If you're working in a client side javascript, then use this approach:


crmForm.all.lookupschemaname.DataValue = null;


Good luck!



Callout design issue

Last week I noticed some weird behaviour in MS CRM. For a business requirement I created a callout. The callout would perform some business logic as soon as an appointment gets created. The business logic should only run if the activty gets created by a quick campaign. Don't ask me for the reason, but that was requested.

No problem, I created a pre callout which runs on the appointment's OnCreate event. It checks for the regarding field and if this is a quick campaign, then continue, otherwise stop. Fine, I tested the callout by creating an appointment with the regarding field set to an existing quick campaign. After saving the appointment, it appeared that the callout worked.

Unfortunately the testing people did not agree with that test. They tested the callout as it should be used by creating a quickcampaign which finally creates the appointments. In this case it does not work! After some research I have had contact with the product team and they did confirm this behaviour. The quick campaigns do not use the webservice and therefore it will not fire the OnCreate callout. The way to go is to use the MS CRM 1.2 approach which will work. They have promised to work on this for the Titan release, but no feature is in there for sure until it is in the RTM version. Let's just hope that this does gets fixed.

I have updated the list with possible solutions for a callout which is not working. See the list here: Callout not working



Territory manager cannot belong to other territory

If you're working with territories by using the SDK, you might get this error message:


0x80043805: Territory manager cannot belong to other territory


This happens if you want to make a specific user manager of two territories. This is possible, but you will have to make sure that the field 'territory' is set to NULL of this user. When you change the manager field of a territory by using the SDK, then the attribute territory of the user is changed as well. If you want to set this user as manager of the second territory, then you'd first need to clear this field.

Once again, I hope this saves you some time!



How to set the parent account via SDK

Last week I've been working on a migration application. One requirement in the application was to set the parent account of child accounts. It could be that a parent account has a parent account itself as well. So in my application I have followed this approach:
- For each account in the source, create an account in CRM 3.0
- For each account in the source, map the parent account to a crm accout and update the account in CRM 3.0

No big deal. This worked for the first thousands of accounts, but then suddenly an error message showed up:


<detail>
<error>
<code>0x80040237</code>
<description>Operation failed due to a SQL integrity violation.</description>
<type>Platform</type>
</error>
</detail>


After a lot of research and discussing with Luis Mazarío we found out in what situation this error occurres. It appeared that in some cases this error was thrown when the account already has subaccounts and I want to set the parent account. After even more research we found out that the source of this error was the code which I used to update the parent account. I was using a code like this:


Lookup lookup = new Lookup();
lookup.type = EntityName.account.ToString();
lookup.Value = gParentId;

account acc = service.Retrieve(EntityName.account.ToString(), gAccountId, new AllColumns());
acc.parentaccountid = lookup;
service.Update(acc);


Although it works most of the times, it is not the right code. This is the correct code:


Lookup lookup = new Lookup();
lookup.Value = id_parent;
lookup.type = EntityName.account.ToString();

account acc = new account();
acc.accountid = new Key();
acc.accountid.Value = id;
acc.parentaccountid = lookup;
service.Update(acc);


We're still digging a bit more on why the error occurred at all, but I hope this helps you!

Ronald



Showing a sorted list of the entities

When you want to show a list of entities, then you'd need the MetadataService. The code is not that hard to fetch a list of these entities. There's even an example in the SDK. The code to get all entities including custom entities would be:


MetadataService.MetadataService service = new MetadataService.MetadataService();
service.Credentials = System.Net.CredentialCache.DefaultCredentials;
Metadata md = service.RetrieveMetadata(MetadataFlags.EntitiesOnly);
ArrayList alEntities = new ArrayList();

foreach (EntityMetadata em in md.Entities)
{
if (em != null)
alEntities.Add(em);
}


But if you do have this list, then how do you show this in a sorted way? Usually you could just say alEntities.Sort()... Too bad that doesnt work here because the collection exists out of a list of EntityMetadata instances. So how do we do this? Well, just create a EntityMetadataComparer based on the IComparer interface. Here's the code that you'll need:


using System;
using System.Collections;
using MetadataService;

/// <summary>
/// The class EntityMetadataComparer allows a collection of instances of the class EntityMetaData to be compared.
/// Use this Comparer class to sort for instance an ArrayList
///
/// Author: Ronald Lemmen
/// Company: Avanade
/// Date: 19 Jan 2007
/// </summary>

public class EntityMetadataComparer : IComparer
{
public enum SortOrder {
Ascending = 1,
Descending = -1
}

private int _modifier;

/// <summary>
/// EntityMetadataComparer constructor without sorting option. The default sorting option will be used: Ascending
/// </summary>
public EntityMetadataComparer()
{
_modifier = (int)SortOrder.Ascending;
}

/// <summary>
/// EntityMetadataComparer constructor with sorting option.
/// </summary>
/// <param name="order">Sort Order. Set this by using EntityMetadataComparer.SortOrder.[Ascending|Descending]</param>
public EntityMetadataComparer(SortOrder order)
{
_modifier = (int)order;
}

/// <summary>
/// Compare the two EntityMetadata objects based on their DisplayName
/// </summary>
/// <param name="o1">Object 1</param>
/// <param name="o2">Object 2</param>
/// <returns>The int return value of the string.CompareTo function</returns>

public int Compare(Object o1, Object o2)
{
EntityMetadata em1 = (EntityMetadata)o1;
EntityMetadata em2 = (EntityMetadata)o2;

if (em1.DisplayName == null)
em1.DisplayName = "";

if (em2.DisplayName == null)
em2.DisplayName = "";

return em1.DisplayName.CompareTo(em2.DisplayName) * _modifier;
}
}


Now you can sort your ArrayList with the command:

alEntities.Sort(new EntityMetadataComparer());


Or to sort with a sort option set:
alEntities.Sort(new EntityMetadataComparer(EntityMetadataComparer.SortOrder.Descending));


Good luck!



Callout not working?

Lets write a bit more about how to get a callout to work and to make debugging available. I already wrote something about this before in this post: Callout debug: Access is Denied.

So your callout is not fired? Here are some thoughts to get started. Make sure that:

- the callout even should fire. Are you using the right callout? Special attention to the email PreSend and PostDeliver callouts: PreSend is for sending email
and PostDeliver is after you received an email

- you have built your callout in .NET Framework 1.1

- the dll file is placed in the correct directory. By default this would be: "C:\Program Files\Microsoft CRM\Server\bin\assembly"

- you created a callout.config.xml in the same folder. Don't forget the extension xml

- the callout is subscribed correctly in the callout.config.xml. It should look like: http://schemas.microsoft.com/crm/2006/callout/;

A correct example would be: http://schemas.microsoft.com/crm/2006/callout/;

- you reset iis (start, run, iisreset) after placing the new callout files


Okay, now you have checked the basics, then let's continue with the harder things to find out based on an error message. Either from the web interface or the event log (don't forget to check there to see if there is an error)

The error messages I'd like to give some hints on are:

Error (in Web UI):

Could not load type YourCallout from assembly YourCallout, Version=1.0.2512.17524, Culture=neutral, PublicKeyToken=null.
Description: An unhandled exception occurred during the execution of the current web request.
Please review the stack trace for more information about the error and where it originated in the code.


Possible solution:
If your code is written in VB, then make sure that you write the subscription correct. You might think that the class in the subscription should be "YourNamespace.YourCalloutClass", but in fact it should be "YourProjectName.YourNamespace.YourCalloutClass".

Error (from event viewer):
Error: ISV code threw exception: assembly: YourCallout.dll; class: YourNamespace.YourCallout; entity: product, event: postcreate, exception: System.IO.FileLoadException: Access is denied: 'YourCallout.dll'.File name: "YourCallout.dll"


Possible solution: Make sure that the security is set to Full for "Network Service" on your dll files.

Error (from event viewer):
Error: General config file format error: System.Data.SqlClient.SqlException: Could not find stored procedure 'sp_sdidebug'.


Possible solution: After the iisreset first do all the actions that you should do to run the callout and when the callout should have run, then start the debugger on the w3wp process.

Here's another hint for when working with impersonation. When you're using the userContext.UserId to set the callerid value, make sure that you also set the credentials on the webservice as shown below.


service = new CrmService();
service.Credentials = System.Net.CredentialCache.DefaultCredentials;
service.CallerIdValue = new CallerId();
service.CallerIdValue.CallerGuid = userContext.UserId;


Error (from event viewer):
Error: ISV code threw exception: assembly: YourCallout.dll; class: Namespace.Callout; entity: account, event: postupdate, exception: wi: Specified cast is not valid.


Possible solution: The file "Microsoft.crm.platform.callout.base.dll" is located in the assembly folder. Remove the file from that folder, perform an IISReset and restart the workflow service.

Error (from event viewer):
Error: ISV code threw exception: assembly: YourCallout.dll; class: Namespace.Callout; entity: account, event: postupdate, exception: System.IO.FileNotFoundException: File or assembly name Microsoft.Crm.Platform.Callout.Base, or one of its dependencies, was not found.


Possible solution: The CRM server is older then the RTM version. Look in the registery (HKLM\software\Microsoft\MSCRM\CRM_Server_Version). The value should be 3.0.5300.0 or above. If it is not, then reinstall the CRM server with the latests bits.

Error (from event viewer):
Precallout event cannot have pre-/post-image subscription. event: preupdate, entity: entityname


Possible solution: This error appears if you are supplying prevalue and postvalue attributes in the callout.config.xml for a precallout. The prevalue and postvalue attributes are only valid for postcallouts. The pre callout does supply all the changed values. If you need more values, then get them via the service.Retrieve method.

No Error info?
If you cannot find any error information and your callout doesn't do anything, then check the registery. There might be a key named "SetupMode" in HKLM\Software\Microsoft\MSCRM\. If it is indeed there, then modify this key to contain the DWORD value "0". After the change, make sure that you perform an "IISReset" (thanks Jevgenij).

Exceptional situation.
There is one situation known in which callouts do not fire. This is when an activity is created by quick campaigns (possibly also regular campaigns). The callout OnCreate will NOT fire for the activities. You'll need to use the CRM 1.2 approach for this situation. Microsoft promised to work on this in the Titan release. If it will be fixed is still a question though.

Well, these were the issues I could think of now. Another approach would be to temporarily remove all complex codes and start with a simple callout that writes something to the eventlog. You then at least know if your callout is fired or not.

Hope this helps you to get your callout working!



Determine which priviledges are required

I do assume that you do not provide the end user administrator rights on the crm installation. You probably have defined your own security role with the least priviledges the user should posess. Well how do you know what priviledges the user should posess?

There's an update to the online SDK with this information. You can browse to this url and you'll see a list of messages. Under each message the required priviledges are listed.

Now if you have a question like "Which priviledges are required for adding facilities to a resource group?" can be answered with a visit to that page.



Stop using VS 2003 and start using VS 2005 for CRM Development

Most of the development for MS CRM v3.0 I do in Visual Studio 2003. This is because callouts needs to be built on .NET Framework v1.1. All the projects built with Visual Studio are .NET Framework v2.0. Since I dont want to switch between several IDE's all the time, I only use VS 2003.

Today is the day that I found a very interesting blog posting of Arash. See this post for information on how to build your callouts in VS 2005. So, read the post, download his MSI and get rid of Visual Studio 2003!



Optimize MS CRM webservice

Currently I'm working on an import program. I have to import thousands and thousands of records. Usually the crm system is quite fast, but with these huge loads, I'm getting quite anoyed by the performance of the MS CRM webservice. It even causes error messages like "The underlying connection was closed: Unable to connect to the remote server.".

Now there are some performance tweaks available. I wanted to write a post about it, but just before I found an article written by Aaron Elder. This is a must-read for people writing code to import bulk data. However, this page has been removed, but the same information is available at Bill's blog: Blog Move: Speed Racer - Call CRM at speeds that would impress even Trixie



Fetch all records

Have you ever tried to write a code which will get you all records from a specific entity? It's harder then you think it is! Everybody who is a bit aware of the CRM SDK thinks it should be a fetch statement like this:

<fetch mapping='logical'><entity name='account'><attribute name='accountid'/></entity>

WRONG!
This would only give you the first 5000 records in the database! It is written down in the SDK with small letters, but it could drive you crazy..

There are two solutions for this issue.
1) Add a registery setting to specify not to implement MaxRowsPerPage
2) Modify the fetch statement and merge several results

Here are the details for each solution
1st solution
Search in the SDK for the word "TurnOffFetchThrottling". You should add this as DWORD registery setting to HKLM\Software\Microsoft\MSCRM. Set the value to 1. You will now not have the 5000 records limit.

2nd solution
Modify your fetch statement to include paging and count numbers. Store all the data in an DataSet and perform that series of code over and over again as long as there is data coming.

Here's the script you should use to get all accountid's (for clarity and the ease of use I have added a function called "FetchDataSet").


private DataSet FetchAllAccountIds(){
int i=1;
bool bFinished = false;
DataSet dsAllData = new DataSet();
while (bFinished == false)
{
StringBuilder sbFetch = new StringBuilder();
sbFetch.AppendFormat("<fetch mapping='logical' page='{0}' count='5000'>", i);
sbFetch.Append("<entity name='account'>");
sbFetch.Append("<attribute name='accountid'/>");
sbFetch.Append("<attribute name='new_12_accountid'/>");
sbFetch.Append("</entity>");
sbFetch.Append("</fetch>");
DataSet dsTempResult = FetchDataSet(sbFetch.ToString());
dsAllData.Merge(dsTempResult);
if (dsTempResult.Tables[0].Rows[0]["morerecords"].ToString() == "0")
{
bFinished = true;
}
else
{
i++;
}
}
return dsAllData;
}

private DataSet FetchDataSet(string fetchXml)
{
string strResult = service.Fetch(fetchXml);
DataSet ds = new DataSet();
System.IO.StringReader reader = new System.IO.StringReader(strResult);
ds.ReadXml(reader);
return ds;
}


I hope this saves you some time!

Thanks to Andrew Krivosheyenko for the Regedit solution!



Save disabled fields

Hi!

I was reading a posting of Ben Vollmer about "Doing Math in Microsoft CRM aka Calculated Field". This is an interesting article, but I noticed something in the posting that I did not know yet. There is a possibility to submit fields which are disabled! I have been using calculated fields before, but I always enabled the field when submitting the page.

So what is the solution? The use of the ForceSubmit attribute.
crmForm.all.field.ForceSubmit = true;

See the SDK for more info



File or assembly name Microsoft.Crm.MetadataService, or one of its dependencies, was not found

Imagine.. You're writing a wonderful MS CRM 3.0 addon. After writing lines and lines of code, the time is there to run and test the addon. Instead of seeing your fantasic, state-of-the-art, rocket-science, cutting-edge addon, it is this message that arises:

Parser Error Message: File or assembly name Microsoft.Crm.MetadataService, or one of its dependencies, was not found.

Great.

Now how to solve this?

Well, Jonathan Randall has answered that question already in one of the newsgroups. Here's his posting:

The issue is caused because when you place your under application under the Microsoft CRM 3.0 web site, your application inherits the settings from the parent web site. Since the Microsoft CRM application has the two assemblies located in its own \bin folder, Microsoft CRM functions
correctly. However, your application does not have these assemblies in its \bin folder so as a result, the application fails to load. Here is some information that will correct the issue:

Symptoms: When a user attempts to access an ASP.NET web application that has its virtual directory located under the Microsoft CRM v3.0 Web Site in Internet Information Services (IIS), the following error will be displayed:

Server Error in '/' Application.
----------------------------------------------------------------------

Further detail on the error includes the following:

Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Parser Error Message: File or assembly name Microsoft.Crm.MetadataService, or one of its dependencies, was not found.

Source Error:

Line 11: <add assembly="Microsoft.Crm.ManagedInterop, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
Line 12: <add assembly="Microsoft.Crm.MetadataHelper, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
Line 13: <add assembly="Microsoft.Crm.MetadataService, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
Line 14: <add assembly="Microsoft.Crm.NativeInteropProxy,
Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
Line 15: <add assembly="Microsoft.Crm.ObjectModel, Version=3.0.5300.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

Source File: C:\Program Files\Microsoft CRM\CRMWeb\web.config
Line: 13

Assembly Load Trace: The following information can be helpful to determine why the assembly 'Microsoft.Crm.MetadataService' could not be loaded.

=== Pre-bind state information ===
LOG: DisplayName = Microsoft.Crm.MetadataService, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
(Fully-specified)
LOG: Appbase = file:///C:/Program Files/Microsoft CRM/CRMWeb/KBTest
LOG: Initial PrivatePath = bin
Calling assembly : (Unknown).
===

LOG: Publisher policy file is not found.
LOG: No redirect found in host configuration file (C:\WINNT\Microsoft.NET\Framework\v1.1.4322\aspnet.config).
LOG: Using machine configuration file from C:\WINNT\Microsoft.NET\Framework\v1.1.4322\config\machine.config.
LOG: Post-policy reference: Microsoft.Crm.MetadataService, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
LOG: Attempting download of new URL file:///C:/WINNT/Microsoft.NET/Framework/v1.1.4322/Temporary ASP.NET Files/kbtest/e724dea7/f72fefd/Microsoft.Crm.MetadataService.DLL.
LOG: Attempting download of new URL file:///C:/WINNT/Microsoft.NET/Framework/v1.1.4322/Temporary ASP.NET Files/kbtest/e724dea7/ f72fefd/ Microsoft.Crm.MetadataService/ Microsoft.Crm.MetadataService.DLL.
LOG: Attempting download of new URL file:///C:/Program Files/Microsoft CRM/CRMWeb/KBTest/bin/Microsoft.Crm.MetadataService.DLL.
LOG: Attempting download of new URL file:///C:/Program Files/Microsoft CRM/CRMWeb/KBTest/bin/Microsoft.Crm.MetadataService/ Microsoft.Crm.MetadataService.DLL.
LOG: Attempting download of new URL file:///C:/WINNT/Microsoft.NET/Framework/v1.1.4322/Temporary ASP.NET Files/kbtest/e724dea7/ f72fefd/Microsoft.Crm.MetadataService.EXE.
LOG: Attempting download of new URL file:///C:/WINNT/Microsoft.NET/Framework/v1.1.4322/Temporary ASP.NET Files/kbtest/ e724dea7/ f72fefd/ Microsoft.Crm.MetadataService/ Microsoft.Crm.MetadataService.EXE.
LOG: Attempting download of new URL file:///C:/Program Files/Microsoft CRM/CRMWeb/KBTest/bin/Microsoft.Crm.MetadataService.EXE.
LOG: Attempting download of new URL file:///C:/Program Files/Microsoft CRM/CRMWeb/KBTest/ bin/Microsoft.Crm.MetadataService/ Microsoft.Crm.MetadataService.EXE.

----------------------------------------------------------------------------
----
Version Information: Microsoft .NET Framework Version:1.1.4322.2300;
ASP.NET Version:1.1.4322.2300

Cause: Since the ASP.NET web application is located under the Microsoft CRM v3.0 web site, the application is inheriting settings that are part of the Microsoft CRM web site's web.config file. The Web.config file for a specific ASP.NET application is located in the root directory of the application and contains settings that apply to the Web application and inherit downward through all of the subdirectories in its branch. In this case, the Microsoft.Crm.MetadataService.dll and the Microsoft.Crm.Tools.ImportExportPublish.dll assemblies are being loaded by the Microsoft CRM web site. These two assemblies are located in the Microsoft CRM web application's \bin directory. Since these two assemblies are not part of the custom ASP.NET application, the Common Language Runtime begins a probing operation to locate the two assemblies. Since the assemblies are not installed into the Global Assembly Cache (GAC) or in the custom ASP.NET application's \bin folder, the exception is thrown.

Resolution:

There are four solutions to resolve the error. Each solution is detailed below.

Solution 1: Remove <add assemblies> entries in Microsoft CRM v3.0 web site's web config file.

In the <system.web> element of the web.config file, locate the <assemblies> node and remove the following entries:
<add assembly="Microsoft.Crm.MetadataService, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="Microsoft.Crm.Tools.ImportExportPublish, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

Solution 2: Install the Microsoft.Crm.MetadataService.dll and the Microsoft.Crm.Tools.ImportExportPublish.dll into the Global Assembly Cache (GAC).

The two assemblies that can be added to the Global Assembly Cache are located in the \bin directory under the Microsoft CRM web site's installation folder. A typical location would be C:\Program Files\Microsoft CRM\CRMWeb\bin.

Solution 3: Edit the ASP.NET web application's web config to remove the two Microsoft CRM assemblies.

Edit the ASP.NET application's web.config by adding the following entries to the <compilation> element that is nested in the <system.web> element of the file:

<system.web>
<compilation defaultLanguage="C#" debug="true">
<assemblies>
<remove assembly="Microsoft.Crm.MetadataService, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<remove assembly="Microsoft.Crm.Tools.ImportExportPublish, Version=3.0.5300.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</assemblies>
</compilation>
</system.web>

Solution 4: Locate the ASP.NET web application in a separate web site from the Microsoft CRM v3.0 web site.

Using this approach ensures that the application does not inherit any of the settings from the Microsoft CRM web site.

Jonathan Randall
Microsoft Online Support Engineer - MBS CRM Developer Support



Callout debug: Access is Denied

I'm not sure if more people are experiencing the same thing, but at least I'd like to share this information with you.

After creating a debug build of a callout, I do copy the dll and pdb files to the \Microsoft CRM\Server\bin\assembly folder. Now I can normally debug the callout. But after modifying the code and copying the files again, then I cannot step into the code. CRM just steps over the whole callout.

When searching for a reason for this behaviour, I found a message in the event viewer. The message says:
-------------------------------------------------------------------------
Event Type: Error
Event Source: MSCRMCallout
Event Category: None
Event ID: 16912
Date: 6/22/2006
Time: 11:53:00 AM
User: N/A
Computer: CRMTest
Description:
Error: ISV code threw exception: assembly:
Microsoft.Crm.Sdk.FullSample.CalloutSample.dll; class:
Microsoft.Crm.Sdk.FullSample.Callouts.CalloutSample; entity: account,
event: precreate, exception: System.IO.FileLoadException: Access is
denied: 'Microsoft.Crm.Sdk.FullSample.CalloutSample.dll'.
File name: "Microsoft.Crm.Sdk.FullSample.CalloutSample.dll"
at System.Reflection.Assembly.nLoad(AssemblyName fileName, String
codeBase, Boolean isStringized, Evidence assemblySecurity, Boolean
throwOnFileNotFound, Assembly locationHint, StackCrawlMark& stackMark)
at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef,
Boolean stringized, Evidence assemblySecurity, StackCrawlMark&
stackMark)
at System.Reflection.Assembly.LoadFrom(String assemblyFile, Evidence
securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm
hashAlgorithm)
at System.Activator.CreateInstanceFrom(String assemblyFile, String
typeName, Boolean ignoreCase, BindingFlags bindingAttr, Binder binder,
Object[] args, CultureInfo culture, Object[] activationAttributes,
Evidence securityInfo)
at Microsoft.Crm.Callout.CalloutHost.PreCreate(CalloutUserContext
userContext, CalloutEntityContext entityContext, String& entityXml,
String& errorMessage)


=== Pre-bind state information ===
LOG: Where-ref bind. Location = C:\Program Files\Microsoft
CRM\server\bin\assembly\ Microsoft.Crm.Sdk.FullSample.CalloutSample.dll
LOG: Appbase = c:\windows\system32\inetsrvLOG: Initial PrivatePath = NULL
Calling assembly : (Unknown).
===


LOG: Policy not being applied to reference at this time (private,
custom, partial, or location-based assembly bind).
LOG: Attempting download of new URL file:///C:/Program Files/Microsoft
CRM /server/bin/assembly/ Microsoft.Crm.Sdk.FullSample.CalloutSample.dll.


For more information, see Help and Support Center at
http://go.microsoft.com/fwlink/events.asp.
-------------------------------------------------------------------------

Now how to solve this?
The resolution is quite simple. After copying the files to the assembly folder, you should give the user "Network Service" full permissions. The callout will work now and you will be able to debug the code.

Good luck!

Update: Ofcourse you can also give the user "Network Service" full rights on the bin folder of your project. You will not have to modify the file then again and again.



Finally there: Show and hide fields based on the users role!

Hi all,

I've been posting around showing and hiding fields more often. I won't be talking about that now, instead I'll be discussing the ability to perform JavaScript coding based on the users role.

This solution is very neat and very clean, though unsupported!!! Now... Let's get started and dig into this.

MS CRM has a lot of Javascript codes delivered with the product. One of these codes is located here: "/_controls/RemoteCommands/RemoteCommand.js". This file is being included into every page. One of the functions in this file is RemoteCommand(sObject, sCommand, sUrlBase). You can use this function to connect to CRM webservices.

Via Javascript you can get the userid of the currently logged in user via this RemoteCommand. This is done using this code:


var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();


Another webservice which is interesting is the UserManager webservice. By using this function, you can get all roles of the system. The roles to which the user is assigned are marked with "checked='true'". Here's the code to get this list


var command = new RemoteCommand("UserManager", "GetUserRoles");
command.SetParameter("userIds", "<guid>" + userId + "</guid>");
var oResult = command.Execute();


Now if we add this functionality to functions and add some try catches, then you'll end up with this code:


function getUserId()
{
try
{
var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue.UserId;
}
}
catch(e)
{
alert("Error while retrieving userid.");
}
return null;
}

function getUserRoles(userId)
{
try
{
var command = new RemoteCommand("UserManager", "GetUserRoles");
command.SetParameter("userIds", "<guid>" + userId + "</guid>");

var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue;
}
}
catch(e)
{
alert("Error while retrieving roles.");
}
return null;
}


Now we only have to add functions which checks if the user has a specific role. The final code is:


function getUserId()
{
try
{
var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue.UserId;
}
}
catch(e)
{
alert("Error while retrieving userid.");
}
return null;
}

function getUserRoles(userId)
{
try
{
var command = new RemoteCommand("UserManager", "GetUserRoles");
command.SetParameter("userIds", "<guid>" + userId + "</guid>");

var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue;
}
}
catch(e)
{
alert("Error while retrieving roles.");
}
return null;
}

function userHasRole(userId, roleName)
{
result = getUserRoles(userId);
if (result != null)
{
var oXml = new ActiveXObject("Microsoft.XMLDOM");
oXml.resolveExternals = false;
oXml.async = false;
oXml.loadXML(result);

roleNode = oXml.selectSingleNode("/roles/role[name='" + roleName + "']");
if (roleNode != null)
{
if (roleNode.selectSingleNode("roleid[@checked='true']") != null)
return true;
}
}

return false;
}

function currentUserHasRole(roleName)
{
userId = getUserId();
return userHasRole(userId, roleName);
}


Now you can check for roles by using the code listed down here:

if(currentUserHasRole('Salesperson')){
alert('true');
}else{
alert('false');
}


You can copy and paste the functions as well as the last code in the form onload event. This is not really user friendly / readable though. If you copy and paste the functions to the global.js script instead, then you can use the last code snippet in the form onload.

Now. To get back to the subject "show and hide fields based on the users role"... If you modify the last code snippet to contain the showing and hiding fields as discussed in this article, instead of alert('true') and alert('false'), then you've got a very good customized system.

Thanks go out to Steven Brom (Qurius Advanced Solutions - NL) for supplying the codes!

Update 1: Thanks to Josh Painter who pointed me to the fact that I forgot to replace the < sign with <> in the codes.

Update 2: I mentioned that this solution was supported, but since we're using webservices from MS CRM other then the regular webservice, this is unsupported anyway... But still it's cool ;)

Update 3: This code doens't work on the outlook client. I'm still thinking about a solution to that.

Update 4: The code above is created for CRM 3.0. For CRM 4.0 look at the following blogs:
http://www.crowehorwath.com/cs/blogs/crm/archive/2008/05/08/hide-show-fields-in-crm-4-0-based-on-security-role.aspx
http://jianwang.blogspot.com/2008/01/crm-40-check-current-users-security.html