Saturday, December 20, 2008

MS CRM 4.0 Filtered Lookups

I just finished implementing a number of filtered lookups with Michael Höhne's Stunnware Filtered Lookup 4.0. Like most developers I have to think seriously about whether I want to build something vs. pay for someone else's work.

There has been a lot of work put into this product since its introduction with CRM 3.0. The 4.0 release seems pretty well sorted and really makes creating filtered lookups an easy task to accomplish. Supporting the Outlook client, multiple languages, multi-tenancy, and IFD makes this a much better solution then most(probably all) of us would take the time to build on our own.

Filtered Lookups have been reinvented far too many times and become maintenance issues for many upgrading from CRM 3.0 to CRM 4.0. I would rather not have to dig through another guy's failed attempt at this functionality in the future. I personally believe MS should pull this functionality into its product, but barring that, I think using a product like this is the best way to go.

I'm currently using it for a growing list of filtered Lookups for one my customers.  This includes some pretty standard lookups like the Account: Primary Contact Lookup, the Case: Responsible Contact, and the Email To:, CC:, and BCC: fields, as well as some for custom fields.

What is typically involved in making a filtered lookup with this tool is the following:

  1. Settings -> Filtered Lookup: Create a Retrieve Multiple Queries entity
    • This may involve using the Fetch Wizard to generate the query.
  2. Settings -> Filtered Lookup: Create a Single-View Lookup entity
  3. Settings -> Customizations: Add JavaScript to your entity's OnLoad event.

Here is a simple example:

I needed a filtered list for Reseller Accounts on an opportunity.

image

So I created a new Retrieve Multiple Query entity,image

copied the Query from the Active Accounts example and added a filter for an industrycode picklist value of 21,image

created a Single-View Lookup using the Query above,image

setup the columns for that Lookup,image 

set the Labels to English,image

added this JavaScript to my Opportunity's OnLoad Event. Published the opportunity and I was done.

SW_IS_LICENSED_USER = false; try
{ var httpRequest = new ActiveXObject("Msxml2.XMLHTTP"); httpRequest.open("GET", prependOrgName("/isv/stunnware.com/cld4/cld4.aspx?orgname=" + ORG_UNIQUE_NAME), false); httpRequest.send(null); eval(httpRequest.responseText); } catch(e) { } if (SW_IS_LICENSED_USER) { var resellerAccountLookup = new SwSingleLookup("new_resellerid"); if (resellerAccountLookup.existsOnForm()) { resellerAccountLookup.setLookupClass("ResellerAccounts"); } }

All in all that took maybe 10 minutes including testing.

That was a very simple case. The examples in the help file show you how to create the Account: Primary Contact filtered lookup showing only Contacts associated with that Account. Implementing this only required cutting from the example and pasting into the OnLoad Event of the account form. Another example gave me the information I needed to to implement the Case Responsible Contacts Filter where the Filtered Lookup changes when the case's account is changed. This required a little JavaScript added to the OnChange for the customerid as well as the Onload for the form.

The email modifications I made were also very easy to implement as shown in the code that follows. Since the Account is almost always preloaded in the To: field, it was easy to overload the lookups for the target selections to default to the Contacts related to that Account.

SW_IS_LICENSED_USER = false;

try {
    var httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    httpRequest.open("GET", prependOrgName("/isv/stunnware.com/cld4/cld4.aspx?orgname=" + ORG_UNIQUE_NAME), false);
    httpRequest.send(null);
    eval(httpRequest.responseText);
}

catch(e) {
}

if (SW_IS_LICENSED_USER) 
{  
   if ( crmForm.all.to.DataValue != null )
   {
     var accountid =  crmForm.all.to.DataValue[0].id;
    
     var toContactLookup = new SwSingleLookup("to");
     toContactLookup.setParameter("parentcustomerid", accountid);
     toContactLookup.setLookupClass("AssociatedContacts");

     var ccContactLookup = new SwSingleLookup("cc");
     ccContactLookup.setParameter("parentcustomerid", accountid);
     ccContactLookup.setLookupClass("AssociatedContacts");

    var bccContactLookup = new SwSingleLookup("bcc");
    bccContactLookup.setParameter("parentcustomerid", accountid);
    bccContactLookup.setLookupClass("AssociatedContacts");
   }

}

For this instance I am still relying on the Form Assistant if the end user wants to select multiple Contacts for a single field or add a CRM user, which is a rare case for this customer. The Form Assistant is not modified by these lookups in any way.

Understanding 3 Important Lines of Code
There are three important lines of code to that you will use to create a filtered lookup.

1. Create a new lookup to override the existing lookup for specified field on the form. In this case the "to" field on the email form. 

      var toContactLookup = new SwSingleLookup("to");

2. Set a filter parameter that is defined in your Fetch and assign a value. 

      toContactLookup.setParameter("parentcustomerid", accountid);

Below is the Fetch that you can access under settings. Notice the parentcustomerid field in the filter section. You are not limited to a single parameter if you need additional criteria for your filter.

<fetch mapping="logical">
   <entity name="contact">
      <attribute name="emailaddress1" />
      <attribute name="telephone1" />
      <attribute name="fullname" />
      <order attribute="fullname" />
      <filter>
         <condition attribute="statecode" operator="eq" value="0" />
         <condition attribute="parentcustomerid" operator="eq" param="parentcustomerid" />
      </filter>
   </entity>
</fetch>


3. Set the class that you are using.  

     toContactLookup.setLookupClass("AssociatedContacts");

That's it, just save your customization and publish. You now have a working filtered Lookup.

Just the Beginning 
These are some very simple examples, but it is obvious how easily much more complex filters could be. If you are going to create complex Retrieve queries, it is worth downloading the Fetch Wizard as well, which is a really easy way to create the XML needed for the fetches that this tool relies on. The Fetch Wizard is part of the Stunnware Tools 4.0. that are available for download at no charge.

What you will see Under Settings-> Filtered Lookup are the following which will allow you to configure everything but the little bit of JavaScript that you will add to your forms, and there are even tools for tracing your filtered lookups if something isn't working quite right.image

This tool is made for a developer to use, but a person comfortable with customizing CRM and with some JavaScript experience should be able to get by. It requires an installation process that can require you to manually update the XML of your ISV.config and SiteMap, but if you read the instructions, it will all make sense.

If you are playing with the MS CRM 4.0 VPC Image, there is a license for that image that you can download at no cost to test this system out.

If you are considering reinventing this wheel, I seriously recommend trying this solution out first.

Monday, December 15, 2008

MS CRM 4.0 Plug-ins vs. CRM 3.0 Callouts

Plug-ins are a huge improvement over the callouts available in MS CRM 3.0. They are much more flexible, much easier to deploy, and have a many more triggers allowing a lot more access to the inner operation of your CRM system.

The following comparison shows some of the key differences:

Callout

Plug-in

Deployment

Requires Workflow service to be restarted to re-read the callout.config.xml which defines which assemblies and classes to call for each triggered event.

Requires assemblies be copied to each CRM server in your web server farm. Because those assemblies can be busy this can require:

  1. Draining each server in an NLB configuration.
  2. An IIS reset
  3. Stopping the Workflow service.
  • Allows deployment to the database in a single step for all CRM servers.
  • Or deployment to the GAC on each server.
  • Or deployment to a folder for debugging.

 

  • Requires a strongly typed assembly.

Flexibility

Requires a different interface for each Message. A PreCreate has a different interface from a PostCreate, or a PreUpdate, or a PostUpdate. Has the same interface for all messages.

Supports many more messages
Supported Messages for Plug-ins

  Has parent and child pipeline feature
  Has the ability to watch for endless loops.

Coding

Requires parsing Xml images to see the contents of the entity generating the message: preImageEntityXml, postImageEntityXml

Returns a Dynamic Entity or Moniker instead.

var entity = (DynamicEntity)context.InputParameters.Properties[ParameterName.Target];

var myMoniker = (Moniker)context.InputParameters.Properties[ParameterName.Target];

Allows validity checking and stopping a change to the database in a preCallout

errorMessage = "Validation Failed."; return PreCalloutReturnValue.Abort;
                

Allows validity checking and stopping an operation in a Pre message.

throw new InvalidPluginExecutionException(
"Validity Failed. {1}");

Allows modifying data before it is written to the database in a  preCallout.

Parse and Modify the entityXML               

Allows modifying data before it is written to the database in a Pre message.

Modify the Dynamic entity.

String nameProperty = entity.Properties["name"] as String;
nameProperty = "new name";


Resources

VS Plug-in template This template will create a basic Plug-in Project and is a good starting point.

  • The example it creates is for a Dynamic Entity Target. This is useful for Create and Update messages, but many other messages will return a Moniker which gives you the id of the calling entity.
  • The template includes code to call a customized crmservice which can be useful, although it is faster and a better practice to call your service from the context as shown below if you are comfortable with Dynamic Entities:

    ICrmService service = context.CreateCrmService(true);

Plugin Deployment Tool This tool will allow you a simple way to deploy your plug-ins. and has an easy to use UI.

With plug-ins it is important to learn about Dynamic Entities.

If you purchase David Yak's CRM as a Rapid Development Platform, it comes with a number of helper classes for Dynamic Entities, and plug-ins as well as other useful tools that are interesting. His chapters on plug-ins and dynamic entities are useful, and the code he provides is worth downloading and referring to. He also has chapters specific to using his plug-in framework for debugging and other tasks which are not CRM development generic.

I recently ordered a copy of Programming Microsoft Dynamics CRM 4.0, and I'll try to remember to update this post after I have looked over its coverage of Plug-ins and dynamic entities.

Wednesday, November 26, 2008

Interactive Webcast: The Top 5 Ways to Save Money with CRM

I've been invited back to be a guest speaker for an interactive Webcast on December 4th at 10:00 AM Pacific, 1:00 PM Eastern, 6:00PM GMT. This is through CBS interactive and Tech Republic.

http://webcasts.techrepublic.com.com/abstract.aspx?docid=390610&promo=100202

Thursday, November 20, 2008

Queues in MS CRM

There are a lot of misconceptions about queues. They only apply to cases and activities.  While the terminology is the same, assigning a case or activity to a queue is not the same as assigning an entity to a CRM user or team. Programmatically what is being used is a RouteRequest rather than an AssignRequest.

To get a better idea how this works we'll look at database. There are two entities storing queue information, the Queue and the QueueItem. These are not customizable.

Queue – (table) contains all queues

  • There are two queues automatically created for each CRM user. 
    • Assigned
    • In Progress
  • Any user defined queues that you create are also stored here.
    • example:  My New Public Queue

QueueItem – (link table)  Contains an entry for each entity assigned to a queue.

  • An item can only belong to one Queue at a time,
  • The only entities that can below to a Queue are:
    • Activities ( email, tasks, etc. )
    • Cases

An activity or case retains its owner and is not modified in any way when it is assigned to a queue. It is just added to the QueueItem list.  An entity can ONLY belong to one queue at a time.

Normal Lifecycle

When created a case or activity is added to the Assigned queue of the owner and it will stay there until it is completed/closed/canceled.

Sometimes a workflow will assign an entity to a user defined queue. (Below is an example that just puts all created Cases into a Support queue.) You might want logic with timers to move Cases around, or you might have different queues by subject, or related account territory. The important thing is that the queue is used as a natural part of the customer's business process.

image

When someone selects an entity in the queue and clicks  Accept... that entity it is then moved into that person's "In Progress" queue.

image

Example code working with Queues

Creating a Queue

var newQueue = new queue
{
    name = "My New Public Queue",
    businessunitid = new Lookup { Value = BusinessUnitId, type = EntityName.businessunit.ToString() },
    primaryuserid = new Lookup { Value = UserId, type = EntityName.systemuser.ToString() },
    queuetypecode = new Picklist { name = "Public", Value = 1 }
};

service.Create(newQueue);

Assigning an incident to a Queue

// Target the incident var target = new TargetQueuedIncident { EntityId = incidentId }; // Create a RouteRequest ( you would need to query for your Queue Id's ) var route = new RouteRequest { Target = target, RouteType = RouteType.Queue,
SourceQueueId = currentQueueId.Value EndpointId = finalQueueId, }; // Execute the route var routed = (RouteResponse)service.Execute(route);

Tracking Queue Assignment in a Plugin

One of the related improvements to MS CRM 4.0 is the inclusion of the Route Message for Plug-ins.  For example, you can now trigger based on a Case being routed to a queue even though the case entity is not modified in any way.

Your code can inspect the SourceQueueId to see what queue it is coming from and the EndpointId to see the destination queue.

This link shows a complete list of the Plug-in Message Input Parameters. You can see that the Route Message has 3 input parameters, two which I mentioned (SourceQueueId and EndpointId) as well as RouteType which has three values ( Auto = 0 ( automatic route) , User = 1 (route to a user's private queue), Queue =  2 (route to a public queue) ) You could use the RouteType to filter out a chunk of queue routes that wish to ignore.

The fact that the dynamic entity of an incident contains no information about the queue it is assigned to means that you need to examine the context.InputParameters.Properties to find out which queue the case is coming from and which queue it will finally call home.

public void Execute(IPluginExecutionContext context)
   {
      DynamicEntity entity = null;

    if (context.InputParameters.Properties.Contains(ParameterName.Target) &&
       context.InputParameters.Properties[ParameterName.Target] is DynamicEntity)
    {
        entity = (DynamicEntity)context.InputParameters.Properties[ParameterName.Target];
        
        if (entity.Name != EntityName.incident.ToString()) { return; }
        if (context.MessageName != MessageName.Route.ToString()) { return; }
        
        Guid SourceQueueId = ((Moniker)context.InputParameters.Properties["SourceQueueId"]).Id;
        Guid EndpointId = ((Moniker)context.InputParameters.Properties["EndpointId"]).Id;
    }

Wednesday, November 19, 2008

A Couple Data import issues

I thought these items might be of help to other people populating data in MS CRM systems. The first is documented, but for the second I found no information on web.

Issue 1: When importing email activities, like all other activities in the vast majority of instances you need to complete them so they show up in history and not in the current Activities of a user's Workplace or against the items to which they are regarded.

If you import an email message with a From email address that is not a CRM system user, you can create the activity without a problem, but will get a Soap Exception "The specified sender type is not supported." When you try to set the state of the email to "Received". The following hot fix will allow you to Complete those incoming emails and set them to "Received".

http://support.microsoft.com/kb/947860/en-us

Issue 2:  While importing account information after a customer of mine added additional customertypecode picklist items I ran into a Soap Exception.

<code>0x8004431a</code>
<description>A validation error occurred.  The value of 'customertypecode' on record of type 'account' is outside the valid range.</description>

The customertypecode picklist starts at 1 and progresses linearly with the default values out of the box, and you can always rename the existing items, but any new picklist items start with values of 200001 and progress linearly from there.

Workaround: Edit an existing account or add a dummy account and select a new pick list item with a higher ranged value and save that account record. Now the problem magically disappears.

Hypothesis: I'm thinking that the CRM webservices are using reflection like Excel does on a spreadsheet column but against the database and it initially ranges the pick list field as a short, but only ranges it up when a larger value exists.

If anyone has a better understanding of why this behavior occurs, I would love to hear about it.

Wednesday, November 12, 2008

Interactive Webcast: Using CRM to Drive Sales in a Slowing Economy

I've been invited to be the guest speaker for an interactive Webcast on November 13th at 1:00 PM Pacific, 4:00 PM Eastern, 9:00PM GMT. This is through CBS interactive and Tech Republic.

http://webcasts.techrepublic.com.com/abstract.aspx?docid=390611

Friday, November 7, 2008

Annotation Import web service error

During a recent import I was getting some exceptions while adding annotations to Accounts and Contacts. The exceptions being generated were not SoapExceptions, but HTTP 400 malformed http request errors.

This was a data driven problem and I assumed that it was an illegal character messing up the SOAP. The annotation.notetext attribute was the only one being set to a large chunk of unknown text. Debugging this showed some illegal characters in my source data. Initially I found an occasional "\0" which is easily removed, but there was another character that showed up as a box symbol.

So how do you filter that out?

First you need to know what you are looking at, so while debugging copy the offending character into the clipboard and paste it into a text file. In Visual Studio there is a binary editor.

File -> Open File
After selecting your file click notice the small pick list to the right of Open.
Select Open With...

evilcharacter

Viewing the text file revealed that the offending character was a hex 12. I had copied three of them into NotePad. For the curious among you a hex 12 = DC2 or Device Control 2.

hex_editor

Armed with that information the following statement filtered out the offending characters and the import completed without error.

newAnnotation.notetext = note.Replace('\0', ' ').Replace('\u0012', ' ');

Below is a code example using this filter to generate contact annotations.

var newAnnotation = new annotation
       {
          objectid = CrmTypes.CreateLookup(EntityName.contact.ToString(), contactId),
          objecttypecode = CrmTypes.CreateEntityNameReference(EntityName.contact.ToString()),
          isdocument = CrmTypes.CreateCrmBoolean(false),
          ownerid = newContact.ownerid,
          subject = "GoldMine notes",
          notetext = note.Replace('\0', ' ').Replace('\u0012', ' ')
       };

try
{
    service.Create(newAnnotation);
}

This could have been solved a number of other ways like a regex that only allows valid characters for a SOAP request, but I haven't run into this issue enough to create one.

Sunday, October 26, 2008

Creating an ActivityParty in MS CRM 4.0

In an earlier Blog I covered creating activities, but did not cover the activityparty in depth. Each activity has a number of activityparty arrays containing lookups to the CRM systemuser, contact, lead, and account entities that are involved in that activity.

For many purposes the only attributes that need to be set in each ActivityParty are the participationtypemask and partyid. The name participationtypemask is a little misleading since it needs to be set to a picklist with a value from the ActivityPartyType rather than actually being a bitmask like the SecurityPrincipal AccessMask.

There are numerous ActivityPartyTypes used for the different ActivityParty arrays in each activity. A complete list of  these types is located here MSDN ActivityPartyType Class. The partyid is just a lookup to the appropriate systemuser, contact, lead or account.

Typically when importing activities you will be matching against an email address and/or contact or lead name. I'd included some example code that will creates an activityparty array for use in creating an activity based on email addresses. A few examples of how I would call this are the following:

thisEmail.to = CreateActivityParty(to_rfc882, ActivityPartyType.ToRecipient);
thisEmail.from = CreateActivityParty(from_rfc882, ActivityPartyType.Sender);
thisEmail.cc = CreateActivityParty(cc_rfc882, ActivityPartyType.CcRecipient);
thisEmail.bcc = CreateActivityParty(bcc_rfc882, ActivityPartyType.BccRecipient);

This shows an email entity having a number of attributes assigned an appropriate activityparty array.

This code takes the emailAddresses string from a given email and creates a list of email addresses that will be part of the ActivityParty array. Then it looks for a match for each email address against systemusers, contacts, leads and finally accounts. Notice that an ActivityParty array can consist of any combination of these entities.

/// <summary>
/// Create that list of Parties associated with each email address in the email list
/// </summary>
/// <param name="emailAddresses"></param>
/// <param name="addressType"></param>
/// <returns></returns>
private activityparty[] CreateActivityParty(string emailAddresses, int addressType)
{
    var activityPartiesPerEmail = new List<activityparty>();

    List<string> addresses = CleanEmail(emailAddresses);  // Get a clean email list from the rfc822 address line.

    //Find All Activity parties 
    
    // Pecking order System Users, Contacts, Leads, Accounts

    foreach ( var emailAddress in addresses)
    {
        BusinessEntityCollection users = FindEntitiesByAttribute(emailAddress, EntityName.systemuser.ToString(), "internalemailaddress");

        if (users.BusinessEntities.Length > 0)
        {
            foreach (systemuser user in users.BusinessEntities)
            {
                activityPartiesPerEmail.Add( new activityparty
                    {
                        participationtypemask = CrmTypes.CreatePicklist(addressType),
                        partyid = CrmTypes.CreateLookup(EntityName.systemuser.ToString(), user.systemuserid.Value)
                    }
                  );
                break;
            }
        }
        else
        {
            BusinessEntityCollection contacts = FindEntitiesByAttribute(emailAddress, EntityName.contact.ToString(), "emailaddress1");

            if (contacts.BusinessEntities.Length > 0)
            {
                foreach (contact aContact in contacts.BusinessEntities)
                {
                    activityPartiesPerEmail.Add(new activityparty
                          {
                              participationtypemask = CrmTypes.CreatePicklist(addressType),
                              partyid = CrmTypes.CreateLookup(EntityName.contact.ToString(), aContact.contactid.Value)
                          }
                        );
                    break;
                }
            }
            else
            {
                BusinessEntityCollection leads = FindEntitiesByAttribute(emailAddress, EntityName.lead.ToString(), "emailaddress1");

                if (leads.BusinessEntities.Length > 0)
                {
                    foreach (lead aLead in leads.BusinessEntities)
                    {
                        activityPartiesPerEmail.Add(new activityparty
                            {
                                participationtypemask = CrmTypes.CreatePicklist(addressType),
                                partyid = CrmTypes.CreateLookup(EntityName.lead.ToString(), aLead.leadid.Value)
                            }
                          );
                        break;
                    }
                }
                else
                {
                    BusinessEntityCollection accounts = FindEntitiesByAttribute(emailAddress, EntityName.account.ToString(), "emailaddress1");

                    if (accounts.BusinessEntities.Length > 0)
                    {
                        foreach (account anAccount in accounts.BusinessEntities)
                        {
                            activityPartiesPerEmail.Add(new activityparty
                                {
                                    participationtypemask = CrmTypes.CreatePicklist(addressType),
                                    partyid = CrmTypes.CreateLookup(EntityName.account.ToString(), anAccount.accountid.Value)
                                }
                            );
                            break;
                        }
                    }
                }
            }
        }
    }
    
    var theseParties = new activityparty[activityPartiesPerEmail.Count];

    activityPartiesPerEmail.CopyTo(theseParties);
    
    return theseParties;
}

/// <summary>
/// Returns all entities of a specified type filterd on a specified attribute and value
/// </summary>
/// <param name="emailAddress">attribute filter value</param>
/// <param name="entity">entityName</param>
/// <param name="attributeName">attribute to filter on</param>
/// <returns></returns>
public BusinessEntityCollection FindEntitiesByAttribute(string emailAddress, string entity, string attributeName)
{
    var condition = new ConditionExpression
    {
        AttributeName = attributeName,
        Operator = ConditionOperator.Equal,
        Values = new[] { emailAddress }
    };


    var filter = new FilterExpression
    {
        FilterOperator = LogicalOperator.And,
        Conditions = new[] { condition }
    };


    var query = new QueryExpression
    {
        EntityName = entity,
        ColumnSet = new AllColumns(),
        Criteria = filter
    };

    try
    {
        return service.RetrieveMultiple(query);
    }

    catch (SoapException ex)
    {
        string errormsg = String.Format("Error for FindSystemUserByEmailAddress {0} error {1}", emailAddress, ex.Detail.InnerText);

        WriteErrorFile(errormsg);
        throw;
    }
}

If you have any questions please let me know.

Friday, October 17, 2008

Creating Attachments to emails and annotations

MS CRM stores attachments directly in the database which allows security to be enforced on attachments. If you delete a entity, all of its annotations are removed with it. By way of comparison this offers a lot more control than just saving a pathname for an attached file like GoldMine does with its RFC822 format emails.

In MS CRM 4.0 the attachments are stored in a text field in the ActivityMimeAttachment table, and in an nvarchar(max) in the AnnotationBase table.  You can use SQL "like" comparator in a where clause against an nvarchar but not for a text field. nvarchar(max) was introduced with SQL 2005.

Creating an activitymimeattachment ( Email attachment )

The following is a code snippet from a larger method that creates the email entity as well. This code is inside a try catch for the email creation.

Notice that a file can be converted into a format that can be saved into the activitymimeattachment.body attribute by using the File.ReadAllBytes() and the Convert.ToBase64String() methods.

// If you have an email entity ready to be created and list of pathnames
// the following code will generate the related activitymimeattachment entities 

Guid emailId = service.Create(newEmail);
 
 if (attachmentPathnames.Count > 0)
 {
     int attachmentCount = 1;

     foreach (var attachmentPathName in attachmentPathnames)
    {
     var emailAttachment = new activitymimeattachment
             {
                // Relate back to email activity 
                activityid = CrmTypes.CreateLookup(EntityName.email.ToString(), emailId),
                filename = attachmentPathName.Substring(attachmentPathName.LastIndexOf('\\') + 1), 
                subject = subject,
                attachmentnumber = CrmTypes.CreateCrmNumber(attachmentCount ++)
             };

     if (File.Exists(attachmentPathName))
     {         
         byte[] fileContents = File.ReadAllBytes(attachmentPathName);
         emailAttachment.body = Convert.ToBase64String(fileContents);
         emailAttachment.filesize = CrmTypes.CreateCrmNumber((int) new FileInfo(attachmentPathName).Length);
         emailAttachment.mimetype = GetMimeType(attachmentPathName); // GetMimeType() Covered Below
     }
     else
     {
         // Handle condition where file doesn't exist
         emailAttachmentbody = "Attachment File Not Found.";
     }

     try
     {
         service.Create(emailAttachment);
     }

     catch (SoapException ex)
     {
         string errormsg = String.Format("Write Error for Email Attachment {0} error {1}", attachmentPathName, ex.Detail.InnerText);
         WriteErrorFile(errormsg);
     }
    }
 }

Creating an annotation (Notes attachment)

The following code creates an annotation which can belong to any entity with a Notes tab. In this case it is a Letter activity. This is very similar to the above code for the activitymimeattachment. One key difference is that an annotation can also just be a Note,  so it has an attribute just to specify if it is a note or an attachment.

Notice the isdocument attribute of the annotation. This needs to be set to true for an attachment. Also notice the overriddencreatedon attribute. The addition of this is one of the many improvements in MS CRM 4.0 and removed one of my pet peeves about MS CRM 3.0. When importing annotations in MS CRM 3.0 all of the annotations would be set to the import date which is useless for the end user. The ONLY way to change it was to go back with a SQL Update to change the createdon date. Now we have the overriddencreatedon attribute so that we don't have to "break the rules" and access the data tables directly.

Guid letterId = service.Create(newLetter); var newAnnotation = new annotation { objectid = CrmTypes.CreateLookup(EntityName.letter.ToString(), letterId), objecttypecode = CrmTypes.CreateEntityNameReference(EntityName.letter.ToString()), isdocument = CrmTypes.CreateCrmBoolean(false), ownerid = newLetter.ownerid, subject = subject, overriddencreatedon = newLetter.actualend, notetext = "File from import." }; if (hasAttachment) { if (File.Exists(attachmentPath)) { newAnnotation.isdocument = CrmTypes.CreateCrmBoolean(true), newAnnotation.filename = attachmentPath.Substring(attachmentPath.LastIndexOf('\\') + 1); byte[] fileContents = File.ReadAllBytes(attachmentPath); newAnnotation.documentbody = System.Convert.ToBase64String(fileContents); newAnnotation.filesize = CrmTypes.CreateCrmNumber((int)new FileInfo(attachmentPath).Length); newAnnotation.mimetype = GetMimeType(attachmentPath); } else { // Handle missing attachment
newAnnotation.subject += " - (attached document not found)"; } try { service.Create(newAnnotation); } catch (SoapException ex) { string errormsg = String.Format("Write Error for Letter Attachment {0} error {1}", attachmentPath, ex.Detail.InnerText); WriteErrorFile(errormsg); } }

The following method is a helper to look up a mimetype for a given file extension.

public string GetMimeType(string fileName)
{
    string mimeType;

    switch (System.IO.Path.GetExtension(fileName).ToLower())
    {
        case ".3dm": mimeType = "x-world/x-3dmf"; break;
        case ".3dmf": mimeType = "x-world/x-3dmf"; break;
        case ".a": mimeType = "application/octet-stream"; break;
        case ".aab": mimeType = "application/x-authorware-bin"; break;
        case ".aam": mimeType = "application/x-authorware-map"; break;
        case ".aas": mimeType = "application/x-authorware-seg"; break;
        case ".abc": mimeType = "text/vnd.abc"; break;
        case ".acgi": mimeType = "text/html"; break;
        case ".afl": mimeType = "video/animaflex"; break;
        case ".ai": mimeType = "application/postscript"; break;
        case ".aif": mimeType = "audio/aiff"; break;
        case ".aifc": mimeType = "audio/aiff"; break;
        case ".aiff": mimeType = "audio/aiff"; break;
        case ".aim": mimeType = "application/x-aim"; break;
        case ".aip": mimeType = "text/x-audiosoft-intra"; break;
        case ".ani": mimeType = "application/x-navi-animation"; break;
        case ".aos": mimeType = "application/x-nokia-9000-communicator-add-on-software"; break;
        case ".aps": mimeType = "application/mime"; break;
        case ".arc": mimeType = "application/octet-stream"; break;
        case ".arj": mimeType = "application/arj"; break;
        case ".art": mimeType = "image/x-jg"; break;
        case ".asf": mimeType = "video/x-ms-asf"; break;
        case ".asm": mimeType = "text/x-asm"; break;
        case ".asp": mimeType = "text/asp"; break;
        case ".asx": mimeType = "video/x-ms-asf"; break;
        case ".au": mimeType = "audio/basic"; break;
        case ".avi": mimeType = "video/avi"; break;
        case ".avs": mimeType = "video/avs-video"; break;
        case ".bcpio": mimeType = "application/x-bcpio"; break;
        case ".bin": mimeType = "application/octet-stream"; break;
        case ".bm": mimeType = "image/bmp"; break;
        case ".bmp": mimeType = "image/bmp"; break;
        case ".boo": mimeType = "application/book"; break;
        case ".book": mimeType = "application/book"; break;
        case ".boz": mimeType = "application/x-bzip2"; break;
        case ".bsh": mimeType = "application/x-bsh"; break;
        case ".bz": mimeType = "application/x-bzip"; break;
        case ".bz2": mimeType = "application/x-bzip2"; break;
        case ".c": mimeType = "text/plain"; break;
        case ".c++": mimeType = "text/plain"; break;
        case ".cat": mimeType = "application/vnd.ms-pki.seccat"; break;
        case ".cc": mimeType = "text/plain"; break;
        case ".ccad": mimeType = "application/clariscad"; break;
        case ".cco": mimeType = "application/x-cocoa"; break;
        case ".cdf": mimeType = "application/cdf"; break;
        case ".cer": mimeType = "application/pkix-cert"; break;
        case ".cha": mimeType = "application/x-chat"; break;
        case ".chat": mimeType = "application/x-chat"; break;
        case ".class": mimeType = "application/java"; break;
        case ".com": mimeType = "application/octet-stream"; break;
        case ".conf": mimeType = "text/plain"; break;
        case ".cpio": mimeType = "application/x-cpio"; break;
        case ".cpp": mimeType = "text/x-c"; break;
        case ".cpt": mimeType = "application/x-cpt"; break;
        case ".crl": mimeType = "application/pkcs-crl"; break;
        case ".crt": mimeType = "application/pkix-cert"; break;
        case ".csh": mimeType = "application/x-csh"; break;
        case ".css": mimeType = "text/css"; break;
        case ".cxx": mimeType = "text/plain"; break;
        case ".dcr": mimeType = "application/x-director"; break;
        case ".deepv": mimeType = "application/x-deepv"; break;
        case ".def": mimeType = "text/plain"; break;
        case ".der": mimeType = "application/x-x509-ca-cert"; break;
        case ".dif": mimeType = "video/x-dv"; break;
        case ".dir": mimeType = "application/x-director"; break;
        case ".dl": mimeType = "video/dl"; break;
        case ".doc": mimeType = "application/msword"; break;
        case ".dot": mimeType = "application/msword"; break;
        case ".dp": mimeType = "application/commonground"; break;
        case ".drw": mimeType = "application/drafting"; break;
        case ".dump": mimeType = "application/octet-stream"; break;
        case ".dv": mimeType = "video/x-dv"; break;
        case ".dvi": mimeType = "application/x-dvi"; break;
        case ".dwf": mimeType = "model/vnd.dwf"; break;
        case ".dwg": mimeType = "image/vnd.dwg"; break;
        case ".dxf": mimeType = "image/vnd.dwg"; break;
        case ".dxr": mimeType = "application/x-director"; break;
        case ".el": mimeType = "text/x-script.elisp"; break;
        case ".elc": mimeType = "application/x-elc"; break;
        case ".env": mimeType = "application/x-envoy"; break;
        case ".eps": mimeType = "application/postscript"; break;
        case ".es": mimeType = "application/x-esrehber"; break;
        case ".etx": mimeType = "text/x-setext"; break;
        case ".evy": mimeType = "application/envoy"; break;
        case ".exe": mimeType = "application/octet-stream"; break;
        case ".f": mimeType = "text/plain"; break;
        case ".f77": mimeType = "text/x-fortran"; break;
        case ".f90": mimeType = "text/plain"; break;
        case ".fdf": mimeType = "application/vnd.fdf"; break;
        case ".fif": mimeType = "image/fif"; break;
        case ".fli": mimeType = "video/fli"; break;
        case ".flo": mimeType = "image/florian"; break;
        case ".flx": mimeType = "text/vnd.fmi.flexstor"; break;
        case ".fmf": mimeType = "video/x-atomic3d-feature"; break;
        case ".for": mimeType = "text/x-fortran"; break;
        case ".fpx": mimeType = "image/vnd.fpx"; break;
        case ".frl": mimeType = "application/freeloader"; break;
        case ".funk": mimeType = "audio/make"; break;
        case ".g": mimeType = "text/plain"; break;
        case ".g3": mimeType = "image/g3fax"; break;
        case ".gif": mimeType = "image/gif"; break;
        case ".gl": mimeType = "video/gl"; break;
        case ".gsd": mimeType = "audio/x-gsm"; break;
        case ".gsm": mimeType = "audio/x-gsm"; break;
        case ".gsp": mimeType = "application/x-gsp"; break;
        case ".gss": mimeType = "application/x-gss"; break;
        case ".gtar": mimeType = "application/x-gtar"; break;
        case ".gz": mimeType = "application/x-gzip"; break;
        case ".gzip": mimeType = "application/x-gzip"; break;
        case ".h": mimeType = "text/plain"; break;
        case ".hdf": mimeType = "application/x-hdf"; break;
        case ".help": mimeType = "application/x-helpfile"; break;
        case ".hgl": mimeType = "application/vnd.hp-hpgl"; break;
        case ".hh": mimeType = "text/plain"; break;
        case ".hlb": mimeType = "text/x-script"; break;
        case ".hlp": mimeType = "application/hlp"; break;
        case ".hpg": mimeType = "application/vnd.hp-hpgl"; break;
        case ".hpgl": mimeType = "application/vnd.hp-hpgl"; break;
        case ".hqx": mimeType = "application/binhex"; break;
        case ".hta": mimeType = "application/hta"; break;
        case ".htc": mimeType = "text/x-component"; break;
        case ".htm": mimeType = "text/html"; break;
        case ".html": mimeType = "text/html"; break;
        case ".htmls": mimeType = "text/html"; break;
        case ".htt": mimeType = "text/webviewhtml"; break;
        case ".htx": mimeType = "text/html"; break;
        case ".ice": mimeType = "x-conference/x-cooltalk"; break;
        case ".ico": mimeType = "image/x-icon"; break;
        case ".idc": mimeType = "text/plain"; break;
        case ".ief": mimeType = "image/ief"; break;
        case ".iefs": mimeType = "image/ief"; break;
        case ".iges": mimeType = "application/iges"; break;
        case ".igs": mimeType = "application/iges"; break;
        case ".ima": mimeType = "application/x-ima"; break;
        case ".imap": mimeType = "application/x-httpd-imap"; break;
        case ".inf": mimeType = "application/inf"; break;
        case ".ins": mimeType = "application/x-internett-signup"; break;
        case ".ip": mimeType = "application/x-ip2"; break;
        case ".isu": mimeType = "video/x-isvideo"; break;
        case ".it": mimeType = "audio/it"; break;
        case ".iv": mimeType = "application/x-inventor"; break;
        case ".ivr": mimeType = "i-world/i-vrml"; break;
        case ".ivy": mimeType = "application/x-livescreen"; break;
        case ".jam": mimeType = "audio/x-jam"; break;
        case ".jav": mimeType = "text/plain"; break;
        case ".java": mimeType = "text/plain"; break;
        case ".jcm": mimeType = "application/x-java-commerce"; break;
        case ".jfif": mimeType = "image/jpeg"; break;
        case ".jfif-tbnl": mimeType = "image/jpeg"; break;
        case ".jpe": mimeType = "image/jpeg"; break;
        case ".jpeg": mimeType = "image/jpeg"; break;
        case ".jpg": mimeType = "image/jpeg"; break;
        case ".jps": mimeType = "image/x-jps"; break;
        case ".js": mimeType = "application/x-javascript"; break;
        case ".jut": mimeType = "image/jutvision"; break;
        case ".kar": mimeType = "audio/midi"; break;
        case ".ksh": mimeType = "application/x-ksh"; break;
        case ".la": mimeType = "audio/nspaudio"; break;
        case ".lam": mimeType = "audio/x-liveaudio"; break;
        case ".latex": mimeType = "application/x-latex"; break;
        case ".lha": mimeType = "application/octet-stream"; break;
        case ".lhx": mimeType = "application/octet-stream"; break;
        case ".list": mimeType = "text/plain"; break;
        case ".lma": mimeType = "audio/nspaudio"; break;
        case ".log": mimeType = "text/plain"; break;
        case ".lsp": mimeType = "application/x-lisp"; break;
        case ".lst": mimeType = "text/plain"; break;
        case ".lsx": mimeType = "text/x-la-asf"; break;
        case ".ltx": mimeType = "application/x-latex"; break;
        case ".lzh": mimeType = "application/octet-stream"; break;
        case ".lzx": mimeType = "application/octet-stream"; break;
        case ".m": mimeType = "text/plain"; break;
        case ".m1v": mimeType = "video/mpeg"; break;
        case ".m2a": mimeType = "audio/mpeg"; break;
        case ".m2v": mimeType = "video/mpeg"; break;
        case ".m3u": mimeType = "audio/x-mpequrl"; break;
        case ".man": mimeType = "application/x-troff-man"; break;
        case ".map": mimeType = "application/x-navimap"; break;
        case ".mar": mimeType = "text/plain"; break;
        case ".mbd": mimeType = "application/mbedlet"; break;
        case ".mc$": mimeType = "application/x-magic-cap-package-1.0"; break;
        case ".mcd": mimeType = "application/mcad"; break;
        case ".mcf": mimeType = "text/mcf"; break;
        case ".mcp": mimeType = "application/netmc"; break;
        case ".me": mimeType = "application/x-troff-me"; break;
        case ".mht": mimeType = "message/rfc822"; break;
        case ".mhtml": mimeType = "message/rfc822"; break;
        case ".mid": mimeType = "audio/midi"; break;
        case ".midi": mimeType = "audio/midi"; break;
        case ".mif": mimeType = "application/x-mif"; break;
        case ".mime": mimeType = "message/rfc822"; break;
        case ".mjf": mimeType = "audio/x-vnd.audioexplosion.mjuicemediafile"; break;
        case ".mjpg": mimeType = "video/x-motion-jpeg"; break;
        case ".mm": mimeType = "application/base64"; break;
        case ".mme": mimeType = "application/base64"; break;
        case ".mod": mimeType = "audio/mod"; break;
        case ".moov": mimeType = "video/quicktime"; break;
        case ".mov": mimeType = "video/quicktime"; break;
        case ".movie": mimeType = "video/x-sgi-movie"; break;
        case ".mp2": mimeType = "audio/mpeg"; break;
        case ".mp3": mimeType = "audio/mpeg"; break;
        case ".mpa": mimeType = "audio/mpeg"; break;
        case ".mpc": mimeType = "application/x-project"; break;
        case ".mpe": mimeType = "video/mpeg"; break;
        case ".mpeg": mimeType = "video/mpeg"; break;
        case ".mpg": mimeType = "video/mpeg"; break;
        case ".mpga": mimeType = "audio/mpeg"; break;
        case ".mpp": mimeType = "application/vnd.ms-project"; break;
        case ".mpt": mimeType = "application/vnd.ms-project"; break;
        case ".mpv": mimeType = "application/vnd.ms-project"; break;
        case ".mpx": mimeType = "application/vnd.ms-project"; break;
        case ".mrc": mimeType = "application/marc"; break;
        case ".ms": mimeType = "application/x-troff-ms"; break;
        case ".mv": mimeType = "video/x-sgi-movie"; break;
        case ".my": mimeType = "audio/make"; break;
        case ".mzz": mimeType = "application/x-vnd.audioexplosion.mzz"; break;
        case ".nap": mimeType = "image/naplps"; break;
        case ".naplps": mimeType = "image/naplps"; break;
        case ".nc": mimeType = "application/x-netcdf"; break;
        case ".ncm": mimeType = "application/vnd.nokia.configuration-message"; break;
        case ".nif": mimeType = "image/x-niff"; break;
        case ".niff": mimeType = "image/x-niff"; break;
        case ".nix": mimeType = "application/x-mix-transfer"; break;
        case ".nsc": mimeType = "application/x-conference"; break;
        case ".nvd": mimeType = "application/x-navidoc"; break;
        case ".o": mimeType = "application/octet-stream"; break;
        case ".oda": mimeType = "application/oda"; break;
        case ".omc": mimeType = "application/x-omc"; break;
        case ".omcd": mimeType = "application/x-omcdatamaker"; break;
        case ".omcr": mimeType = "application/x-omcregerator"; break;
        case ".p": mimeType = "text/x-pascal"; break;
        case ".p10": mimeType = "application/pkcs10"; break;
        case ".p12": mimeType = "application/pkcs-12"; break;
        case ".p7a": mimeType = "application/x-pkcs7-signature"; break;
        case ".p7c": mimeType = "application/pkcs7-mime"; break;
        case ".p7m": mimeType = "application/pkcs7-mime"; break;
        case ".p7r": mimeType = "application/x-pkcs7-certreqresp"; break;
        case ".p7s": mimeType = "application/pkcs7-signature"; break;
        case ".part": mimeType = "application/pro_eng"; break;
        case ".pas": mimeType = "text/pascal"; break;
        case ".pbm": mimeType = "image/x-portable-bitmap"; break;
        case ".pcl": mimeType = "application/vnd.hp-pcl"; break;
        case ".pct": mimeType = "image/x-pict"; break;
        case ".pcx": mimeType = "image/x-pcx"; break;
        case ".pdb": mimeType = "chemical/x-pdb"; break;
        case ".pdf": mimeType = "application/pdf"; break;
        case ".pfunk": mimeType = "audio/make"; break;
        case ".pgm": mimeType = "image/x-portable-greymap"; break;
        case ".pic": mimeType = "image/pict"; break;
        case ".pict": mimeType = "image/pict"; break;
        case ".pkg": mimeType = "application/x-newton-compatible-pkg"; break;
        case ".pko": mimeType = "application/vnd.ms-pki.pko"; break;
        case ".pl": mimeType = "text/plain"; break;
        case ".plx": mimeType = "application/x-pixclscript"; break;
        case ".pm": mimeType = "image/x-xpixmap"; break;
        case ".pm4": mimeType = "application/x-pagemaker"; break;
        case ".pm5": mimeType = "application/x-pagemaker"; break;
        case ".png": mimeType = "image/png"; break;
        case ".pnm": mimeType = "application/x-portable-anymap"; break;
        case ".pot": mimeType = "application/vnd.ms-powerpoint"; break;
        case ".pov": mimeType = "model/x-pov"; break;
        case ".ppa": mimeType = "application/vnd.ms-powerpoint"; break;
        case ".ppm": mimeType = "image/x-portable-pixmap"; break;
        case ".pps": mimeType = "application/vnd.ms-powerpoint"; break;
        case ".ppt": mimeType = "application/vnd.ms-powerpoint"; break;
        case ".ppz": mimeType = "application/vnd.ms-powerpoint"; break;
        case ".pre": mimeType = "application/x-freelance"; break;
        case ".prt": mimeType = "application/pro_eng"; break;
        case ".ps": mimeType = "application/postscript"; break;
        case ".psd": mimeType = "application/octet-stream"; break;
        case ".pvu": mimeType = "paleovu/x-pv"; break;
        case ".pwz": mimeType = "application/vnd.ms-powerpoint"; break;
        case ".py": mimeType = "text/x-script.phyton"; break;
        case ".pyc": mimeType = "applicaiton/x-bytecode.python"; break;
        case ".qcp": mimeType = "audio/vnd.qcelp"; break;
        case ".qd3": mimeType = "x-world/x-3dmf"; break;
        case ".qd3d": mimeType = "x-world/x-3dmf"; break;
        case ".qif": mimeType = "image/x-quicktime"; break;
        case ".qt": mimeType = "video/quicktime"; break;
        case ".qtc": mimeType = "video/x-qtc"; break;
        case ".qti": mimeType = "image/x-quicktime"; break;
        case ".qtif": mimeType = "image/x-quicktime"; break;
        case ".ra": mimeType = "audio/x-pn-realaudio"; break;
        case ".ram": mimeType = "audio/x-pn-realaudio"; break;
        case ".ras": mimeType = "application/x-cmu-raster"; break;
        case ".rast": mimeType = "image/cmu-raster"; break;
        case ".rexx": mimeType = "text/x-script.rexx"; break;
        case ".rf": mimeType = "image/vnd.rn-realflash"; break;
        case ".rgb": mimeType = "image/x-rgb"; break;
        case ".rm": mimeType = "application/vnd.rn-realmedia"; break;
        case ".rmi": mimeType = "audio/mid"; break;
        case ".rmm": mimeType = "audio/x-pn-realaudio"; break;
        case ".rmp": mimeType = "audio/x-pn-realaudio"; break;
        case ".rng": mimeType = "application/ringing-tones"; break;
        case ".rnx": mimeType = "application/vnd.rn-realplayer"; break;
        case ".roff": mimeType = "application/x-troff"; break;
        case ".rp": mimeType = "image/vnd.rn-realpix"; break;
        case ".rpm": mimeType = "audio/x-pn-realaudio-plugin"; break;
        case ".rt": mimeType = "text/richtext"; break;
        case ".rtf": mimeType = "text/richtext"; break;
        case ".rtx": mimeType = "text/richtext"; break;
        case ".rv": mimeType = "video/vnd.rn-realvideo"; break;
        case ".s": mimeType = "text/x-asm"; break;
        case ".s3m": mimeType = "audio/s3m"; break;
        case ".saveme": mimeType = "application/octet-stream"; break;
        case ".sbk": mimeType = "application/x-tbook"; break;
        case ".scm": mimeType = "application/x-lotusscreencam"; break;
        case ".sdml": mimeType = "text/plain"; break;
        case ".sdp": mimeType = "application/sdp"; break;
        case ".sdr": mimeType = "application/sounder"; break;
        case ".sea": mimeType = "application/sea"; break;
        case ".set": mimeType = "application/set"; break;
        case ".sgm": mimeType = "text/sgml"; break;
        case ".sgml": mimeType = "text/sgml"; break;
        case ".sh": mimeType = "application/x-sh"; break;
        case ".shar": mimeType = "application/x-shar"; break;
        case ".shtml": mimeType = "text/html"; break;
        case ".sid": mimeType = "audio/x-psid"; break;
        case ".sit": mimeType = "application/x-sit"; break;
        case ".skd": mimeType = "application/x-koan"; break;
        case ".skm": mimeType = "application/x-koan"; break;
        case ".skp": mimeType = "application/x-koan"; break;
        case ".skt": mimeType = "application/x-koan"; break;
        case ".sl": mimeType = "application/x-seelogo"; break;
        case ".smi": mimeType = "application/smil"; break;
        case ".smil": mimeType = "application/smil"; break;
        case ".snd": mimeType = "audio/basic"; break;
        case ".sol": mimeType = "application/solids"; break;
        case ".spc": mimeType = "text/x-speech"; break;
        case ".spl": mimeType = "application/futuresplash"; break;
        case ".spr": mimeType = "application/x-sprite"; break;
        case ".sprite": mimeType = "application/x-sprite"; break;
        case ".src": mimeType = "application/x-wais-source"; break;
        case ".ssi": mimeType = "text/x-server-parsed-html"; break;
        case ".ssm": mimeType = "application/streamingmedia"; break;
        case ".sst": mimeType = "application/vnd.ms-pki.certstore"; break;
        case ".step": mimeType = "application/step"; break;
        case ".stl": mimeType = "application/sla"; break;
        case ".stp": mimeType = "application/step"; break;
        case ".sv4cpio": mimeType = "application/x-sv4cpio"; break;
        case ".sv4crc": mimeType = "application/x-sv4crc"; break;
        case ".svf": mimeType = "image/vnd.dwg"; break;
        case ".svr": mimeType = "application/x-world"; break;
        case ".swf": mimeType = "application/x-shockwave-flash"; break;
        case ".t": mimeType = "application/x-troff"; break;
        case ".talk": mimeType = "text/x-speech"; break;
        case ".tar": mimeType = "application/x-tar"; break;
        case ".tbk": mimeType = "application/toolbook"; break;
        case ".tcl": mimeType = "application/x-tcl"; break;
        case ".tcsh": mimeType = "text/x-script.tcsh"; break;
        case ".tex": mimeType = "application/x-tex"; break;
        case ".texi": mimeType = "application/x-texinfo"; break;
        case ".texinfo": mimeType = "application/x-texinfo"; break;
        case ".text": mimeType = "text/plain"; break;
        case ".tgz": mimeType = "application/x-compressed"; break;
        case ".tif": mimeType = "image/tiff"; break;
        case ".tiff": mimeType = "image/tiff"; break;
        case ".tr": mimeType = "application/x-troff"; break;
        case ".tsi": mimeType = "audio/tsp-audio"; break;
        case ".tsp": mimeType = "application/dsptype"; break;
        case ".tsv": mimeType = "text/tab-separated-values"; break;
        case ".turbot": mimeType = "image/florian"; break;
        case ".txt": mimeType = "text/plain"; break;
        case ".uil": mimeType = "text/x-uil"; break;
        case ".uni": mimeType = "text/uri-list"; break;
        case ".unis": mimeType = "text/uri-list"; break;
        case ".unv": mimeType = "application/i-deas"; break;
        case ".uri": mimeType = "text/uri-list"; break;
        case ".uris": mimeType = "text/uri-list"; break;
        case ".ustar": mimeType = "application/x-ustar"; break;
        case ".uu": mimeType = "application/octet-stream"; break;
        case ".uue": mimeType = "text/x-uuencode"; break;
        case ".vcd": mimeType = "application/x-cdlink"; break;
        case ".vcs": mimeType = "text/x-vcalendar"; break;
        case ".vda": mimeType = "application/vda"; break;
        case ".vdo": mimeType = "video/vdo"; break;
        case ".vew": mimeType = "application/groupwise"; break;
        case ".viv": mimeType = "video/vivo"; break;
        case ".vivo": mimeType = "video/vivo"; break;
        case ".vmd": mimeType = "application/vocaltec-media-desc"; break;
        case ".vmf": mimeType = "application/vocaltec-media-file"; break;
        case ".voc": mimeType = "audio/voc"; break;
        case ".vos": mimeType = "video/vosaic"; break;
        case ".vox": mimeType = "audio/voxware"; break;
        case ".vqe": mimeType = "audio/x-twinvq-plugin"; break;
        case ".vqf": mimeType = "audio/x-twinvq"; break;
        case ".vql": mimeType = "audio/x-twinvq-plugin"; break;
        case ".vrml": mimeType = "application/x-vrml"; break;
        case ".vrt": mimeType = "x-world/x-vrt"; break;
        case ".vsd": mimeType = "application/x-visio"; break;
        case ".vst": mimeType = "application/x-visio"; break;
        case ".vsw": mimeType = "application/x-visio"; break;
        case ".w60": mimeType = "application/wordperfect6.0"; break;
        case ".w61": mimeType = "application/wordperfect6.1"; break;
        case ".w6w": mimeType = "application/msword"; break;
        case ".wav": mimeType = "audio/wav"; break;
        case ".wb1": mimeType = "application/x-qpro"; break;
        case ".wbmp": mimeType = "image/vnd.wap.wbmp"; break;
        case ".web": mimeType = "application/vnd.xara"; break;
        case ".wiz": mimeType = "application/msword"; break;
        case ".wk1": mimeType = "application/x-123"; break;
        case ".wmf": mimeType = "windows/metafile"; break;
        case ".wml": mimeType = "text/vnd.wap.wml"; break;
        case ".wmlc": mimeType = "application/vnd.wap.wmlc"; break;
        case ".wmls": mimeType = "text/vnd.wap.wmlscript"; break;
        case ".wmlsc": mimeType = "application/vnd.wap.wmlscriptc"; break;
        case ".word": mimeType = "application/msword"; break;
        case ".wp": mimeType = "application/wordperfect"; break;
        case ".wp5": mimeType = "application/wordperfect"; break;
        case ".wp6": mimeType = "application/wordperfect"; break;
        case ".wpd": mimeType = "application/wordperfect"; break;
        case ".wq1": mimeType = "application/x-lotus"; break;
        case ".wri": mimeType = "application/mswrite"; break;
        case ".wrl": mimeType = "application/x-world"; break;
        case ".wrz": mimeType = "x-world/x-vrml"; break;
        case ".wsc": mimeType = "text/scriplet"; break;
        case ".wsrc": mimeType = "application/x-wais-source"; break;
        case ".wtk": mimeType = "application/x-wintalk"; break;
        case ".xbm": mimeType = "image/x-xbitmap"; break;
        case ".xdr": mimeType = "video/x-amt-demorun"; break;
        case ".xgz": mimeType = "xgl/drawing"; break;
        case ".xif": mimeType = "image/vnd.xiff"; break;
        case ".xl": mimeType = "application/excel"; break;
        case ".xla": mimeType = "application/vnd.ms-excel"; break;
        case ".xlb": mimeType = "application/vnd.ms-excel"; break;
        case ".xlc": mimeType = "application/vnd.ms-excel"; break;
        case ".xld": mimeType = "application/vnd.ms-excel"; break;
        case ".xlk": mimeType = "application/vnd.ms-excel"; break;
        case ".xll": mimeType = "application/vnd.ms-excel"; break;
        case ".xlm": mimeType = "application/vnd.ms-excel"; break;
        case ".xls": mimeType = "application/vnd.ms-excel"; break;
        case ".xlt": mimeType = "application/vnd.ms-excel"; break;
        case ".xlv": mimeType = "application/vnd.ms-excel"; break;
        case ".xlw": mimeType = "application/vnd.ms-excel"; break;
        case ".xm": mimeType = "audio/xm"; break;
        case ".xml": mimeType = "application/xml"; break;
        case ".xmz": mimeType = "xgl/movie"; break;
        case ".xpix": mimeType = "application/x-vnd.ls-xpix"; break;
        case ".xpm": mimeType = "image/xpm"; break;
        case ".x-png": mimeType = "image/png"; break;
        case ".xsr": mimeType = "video/x-amt-showrun"; break;
        case ".xwd": mimeType = "image/x-xwd"; break;
        case ".xyz": mimeType = "chemical/x-pdb"; break;
        case ".z": mimeType = "application/x-compressed"; break;
        case ".zip": mimeType = "application/zip"; break;
        case ".zoo": mimeType = "application/octet-stream"; break;
        case ".zsh": mimeType = "text/x-script.zsh"; break;
        default: mimeType = "application/octet-stream"; break;
    }
    return mimeType;
} 

MS CRM 4.0 Attachment Size Limitation

MS CRM stores attachments directly in the database, and there is a 5Mb attachment limit by default with CRM 4.0. This limit is set for good reason, and if you want to increase it, you may want to see if it starts to impact your system performance.

This can be overwritten by doing the following:

  1. Changing the maxRequestLength value in the main CRM web.config file. By default it is 8Mb which doesn't impact the System Setting below. However if you increase the Maximum File size above the maxRequestLength you will have problems.

    <system.web>
        <httpRuntime executionTimeout="300" maxRequestLength="8192"/>
    </system.web>

    There are many examples on the web that show something like the following which pretty well removes any size constraints from the web.config.

    <httpRuntime executionTimeout="9999" maxRequestLength="99999"/>

  2. Save the web.config and restart IIS.
  3. For MS CRM 4.0 that just leaves adjusting the Maximum file size in System Settings with default of 5,120 Kb shown below to something higher but less than the maxRequestLength.

emailAtttachmentSize

Alternate 3. In MS CRM 3.0 there is a registry setting that needs to be set instead. In HKEY_Local_Machine\Software\Microsoft\MSCRM 
the maxuploadfilesize value can be changed from it's 5Mb default.