Tag Archives: MS CRM

MS Dynamics CRM 4.0 Client Configuration Wizard fails with “Outlook is not set as the default mail client” on Windows Server 2008 x64

Whilst trying to install the MS CRM 4.0 client on my laptop today I received the following error:

Microsoft Outlook is not set as the default mail client. Please set Microsoft Outlook as the default mail client from Control Panel\Internet Options\Programs, and then re-run the check.”

Luckily this was fairly easy to diagnose with the help of the excellent Process Monitor utility. All I had to do was setup a filter to only capture events for the “Microsoft.Crm.Client.Config.exe” process by going to Filter->Filter and selecting Process Name is Microsoft.Crm.Client.exe as an inclusion filter. Then using the toolbar deselect the file events and run through the wizard again until I got an output like this:

Here we can see that the registry key [HKLM]\SOFTWARE\Wow6432Node\Clients\Mail\(Default) key is being interrogated, a quick look at this in Registry Editor shows that the default is “Windows Mail” for some reason as shown below:

All we need to do is change this value from “Windows Mail” to “Microsoft Outlook”:

Then the wizard will run through fine!

Summary

I am unsure if this is a Windows Server 2008 issue or a general x64 issue, however to resolve it change
[HKLM]\SOFTWARE\Wow6432Node\Clients\Mail\(Default)
from “Windows Mail” to “Microsoft Outlook”

I am unsure if the Wow6432 key is unique to my machine / configuration.

Advertisements

Closing an MS CRM 3.0 Activity via SDK doesn’t set the ActualEndTime

I’m finding the activities in MS CRM very frustrating to work with, especially the way the states are dealt with.

We just did our first mass-mailing from MS CRM and for the life of me couldn’t work out how to get it to record the activities as closed. Basically we setup a campaign, a marketing list, a campaign activity and then hit “Distribute Campaign Activity”.

However, this just creates me thousands of open activities which effectively show us “to-do” items for the owners of each account (or worse…me!). I googled for ages for a solution to this and only found some piss-poor excuses about it’s up to each Sales person to ‘own’ and deploy the items – ridiculous ! We just sent postal mailers to 1500 people and over 5,000 e-mails!! Sales people are here to sell, not stuff envelopes!

Anyway, having used the MS CRM UI to create the activities I need the activities closed so if a sales guy goes in to the account they can say with confidence “did you receive the mailer we sent you on 15th November?”. My first stab at this basically consisted of:

  1. Creating a dataset and retrieving all of the activities that need altering by querying the filtered views, something like (alter to your needs !!!):
    SELECT     FilteredActivityPointer.activityid, 
    [FilteredActivityPointer].[statecodename] 
    FROM         FilteredCampaignActivity 
    INNER JOIN FilteredCampaignActivityItem 
              ON FilteredCampaignActivity.activityid = FilteredCampaignActivityItem.campaignactivityid 
    INNER JOIN FilteredActivityPointer 
              ON FilteredActivityPointer.regardingobjectid = FilteredCampaignActivity.activityid 
                  AND FilteredCampaignActivity.channeltypecodename = FilteredActivityPointer.activitytypecodename 
    WHERE FilteredActivityPointer.[statecodename]='Open'  -- Only open account/contact activities 
         AND [FilteredCampaignActivity].[subject] LIKE 'Mailer%' -- Name of the Campaign activity (ideally use a Guid instead)
  2. This gives us a list of ActivityIds that need to be ‘closed’, I simply call the GetData method on my dataset to give me a list I can foreach through.
  3. Thus for each open Activity I must iterate through, set the actualendtime and then set the state to close (some helper snippets by David Padbury):
    public static DoClose() 
    { 
    	// Have a datatable called activitiesToBeChanged already populated with rows of type ActivitiesFromCampaignRow 
        foreach (ActivitiesFromCampaignRow row in activitiesToBeChanged.Rows) 
         { 
             activitypointer thisActivity = CrmHelper.RetrieveSingleEntity<activitypointer>(row.activityid, 
                     CreateColumnSet("activityid", "activitytypecode", "statecode")); 
             CloseLetter(thisActivity, ActualEndTime); 
         } 
    } 
    public static ColumnSet CreateColumnSet(params string[] columns) 
    { 
        #region Argument Validation 
        if (columns == null) 
             throw new ArgumentNullException("columns"); 
        if (columns.Length == 0) 
             throw new ArgumentException("There should be at least one column specified.", "columns"); 
        #endregion 
        ColumnSet columnSet = new ColumnSet(); 
        columnSet.Attributes = columns; 
        return columnSet; 
    } 
    public static T RetrieveSingleEntity<T>(Guid id, ColumnSetBase columnSet) 
         where T: BusinessEntity 
    { 
        EntityName entityName = GetEntityNameFromEntityType<T>(); 
        CrmService service = CrmServiceFactory.CreateService(); 
        BusinessEntity entity = service.Retrieve(entityName.ToString(), id, columnSet); 
        return (T)entity; 
    } 
    private static void CloseLetter(activitypointer activity, DateTime ActualEndTime) 
    { 
         CrmService service = CrmServiceFactory.CreateService(); 
        letter thisLetter = RetrieveSingleEntity<letter>(activity.activityid.Value, 
             CreateColumnSet("activityid", "actualend", "subject", "to"));
    
        SetStateLetterRequest setStateRequest = new SetStateLetterRequest() 
        SetStateRequest.EntityId = activity.activityid.Value; 
        TargetUpdateLetter target = new TargetUpdateLetter(); 
        target.Letter = thisLetter;     // Closing the activity doesn't set the end time, so we must do this first 
        if (target.Letter.actualend == null) 
        { 
             target.Letter.actualend = new CrmDateTime();          if (ActualEndTime.Ticks == 0 || ActualEndTime == null) 
                 ActualEndTime = DateTime.Now; 	 target.Letter.actualend.Value = ActualEndTime.ToString("yyyy/MM/ddTHH:mm:ss"); 
    	// Beware SDK is wrong on the formatting here!     } 	UpdateRequest update = new UpdateRequest(); 
    	update.Target = target; 
    	// Now set the time. 
    	UpdateResponse updated = (UpdateResponse)service.Execute(update); 
    
    	public static DoClose() 
    	{ 
    		// Have a datatable called activitiesToBeChanged already populated with rows of type ActivitiesFromCampaignRow 
        	     foreach (ActivitiesFromCampaignRow row in activitiesToBeChanged.Rows) 
    		{ 
    		         activitypointer thisActivity = CrmHelper.RetrieveSingleEntity<activitypointer>(row.activityid,                 CreateColumnSet("activityid", "activitytypecode", "statecode")); 
    			CloseLetter(thisActivity, ActualEndTime); 
    		} 
    	} 
    
    	public static ColumnSet CreateColumnSet(params string[] columns) 
    	{ 
    		#region Argument Validation 
        		if (columns == null) 
    			 throw new ArgumentNullException("columns"); 
    	        if (columns.Length == 0) 
    		throw new ArgumentException("There should be at least one column specified.", "columns"); 
    	    #endregion 
        		ColumnSet columnSet = new ColumnSet(); 
    		    columnSet.Attributes = columns; 
    	    return columnSet; 
    		} 
    public static T RetrieveSingleEntity<T>(Guid id, ColumnSetBase columnSet)
         where T: BusinessEntity 
    {
         EntityName entityName = GetEntityNameFromEntityType<T>(); 
        CrmService service = CrmServiceFactory.CreateService(); 
        BusinessEntity entity = service.Retrieve(entityName.ToString(), id, columnSet); 
        return (T)entity;
     } 
     private static void CloseLetter(activitypointer activity, DateTime ActualEndTime) 
    {
         CrmService service = CrmServiceFactory.CreateService(); 
        letter thisLetter = RetrieveSingleEntity<letter>(activity.activityid.Value,
             CreateColumnSet("activityid", "actualend", "subject", "to")); 
        SetStateLetterRequest setStateRequest = new SetStateLetterRequest();
         setStateRequest.EntityId = activity.activityid.Value;
        TargetUpdateLetter target = new TargetUpdateLetter();
         target.Letter = thisLetter; 
         // Closing the activity doesn't set the end time, so we must do this first
         if (target.Letter.actualend == null)
         {
             target.Letter.actualend = new CrmDateTime(); 
            if (ActualEndTime.Ticks == 0 || ActualEndTime == null)
                 ActualEndTime = DateTime.Now; 
            target.Letter.actualend.Value = ActualEndTime.ToString("yyyy/MM/ddTHH:mm:ss"); // Beware SDK is wrong on the formatting here!
         } 
        UpdateRequest update = new UpdateRequest();
         update.Target = target; 
        // Now set the time.     
        UpdateResponse updated = (UpdateResponse)service.Execute(update); 
        // Now close the activity
         setStateRequest.LetterState = LetterState.Completed;                                // Have to flag as Sent etc.     
    	// from the SDK " Set this field to -1 to have the platform set the appropriate value for the Microsoft CRM application. "
             // Alternatively do something like SELECT * FROM [FilteredStatusMap] WHERE [FilteredViewName] = 'FilteredLetter' to see valid statuses to states
         setStateRequest.LetterStatus = -1; 
        service.Execute(setStateRequest); 
    }

This isn’t meant to be a complete solution, but hopefully will give you enough pointers to be useful, especially with regard to other entity types. I have to say the MS CRM SDK is pretty useless, and the classes are pretty poorly documented

MS CRM 3.0 DMF Gotchas

Having spent over a day grappling with the Microsoft CRM Data Migration Framework v3.1 for MS CRM 3.0 I thought it useful to document some mistakes I made.

  1. In the CDF DO NOT insert rows into cdf_<entity>_info or cdf_<entity>_ext tables as these rows are created by triggers on inserts to the base table. Thus if you need to populate your custom fields in cdf_account_ext you first insert the row in to cdf_account and then UPDATE the auto-created row in cdf_account.
  2. Do NOT populate OwningUser even when you’re sure you have the right lookup to cdf_systembase! Instead populate the OwningUserName column with the username in the form DOMAIN\USER
  3. As (2) do not populate TerritoryID use TerritoryIdName and then use the CDF Data Mapping Wizard (under Cleanse CDF in the DMF app) to map the string to the correct picklist ID.
  4. For an account [name] is a required field
  5. LASTLY BUT MOST IMPORTANT, ensure you are running the DMF using an NT login with access to MS CRM and has a role assigned – a restricted access account does not seem to be sufficient for the DMF to create the rows. This got me for hours, not helped by the fact that the DMF has to be installed on the CRM server and funnily enough we don’t assign expensive CRM licences to the sysadmin accounts!

I really think MS need to look at this tool, the CDF is very good, especially for people who are good with MS SQL, however there is a complete lack of error messages. I lost over 4 hours (see point 5) due to the lack of errors. I’ve encountered the same error when adding accounts via the webservice directly and it generates an exception – why doesn’t DMF expose the exception?