Argos
Phil's CRM Blog | MSCRM

Using Unresolved Email Recipients in CRM 4.0

by Phil Adams 11. August 2010 11:00
Unresolved emails are useful in that you can send an email to someone without having them setup as a system record (contact, account, lead, queue, user, ect).  Unresolved email addresses are turned off by default.  They should be used with caution.  If you send an email to an unresolved address it will not track the email to that lead/contact since one does not exist.  It also does not leverage the email "Do not allow" flags to allow people to opt out of emails.  With those considerations, there are still cases where it is very useful to send an email to someone without having to create a contact record.

I will show two example of sending emails to unresolved recipients.  The first will be sent from the email form using JavaScript.  The second will be done in a plugin.  We will add two unresolved email addresses as CC addresses on emails sent from a contact record.

Before we begin.  You must flip the setting to allow unresolved recipients.  Go to Settings --> Administration --> System Settings --> Email Tab.  Set Allow messages with unresolved e-mail recipients to be sent to yes.

 Second, we will add a couple of fields to the contact for secondary contacts email addresses.  These fields will contain our unresolved email addresses.


JavaScript Implementation
When the user clicks "Send Email" from the contact form we want the CC field to default with the two unresolved email addresses.  To do this we add the following Jscript to the onload of the email form.  The code will check to see if the regarding object is of type contact since this form is used for all emails.  Then it adds each email address to an email object that follows the activityparty schema for unresolved email addresses.  It has a type of 9206 and the email is set in the data property.

if (crmForm.FormType == 1 &&
    crmForm.all.regardingobjectid.DataValue != null &&
    crmForm.all.regardingobjectid.DataValue[0].typename == "contact" &&
    window.opener != null &&
    window.opener.document != null) {

    var ar = new Array();
    var emailObj = new Object();

    if (window.opener.document.crmForm.all.new_secondarycontactemail1 != null &&
            window.opener.document.crmForm.all.new_secondarycontactemail1.DataValue != null) {
        emailObj = new Object();
        emailObj['type'] = '9206';
        emailObj['category'] = '3';
        emailObj['data'] = window.opener.document.crmForm.all.new_secondarycontactemail1.DataValue;
        emailObj['name'] = window.opener.document.crmForm.all.new_secondarycontactemail1.DataValue;
        ar.push(emailObj);
    }

    if (window.opener.document.crmForm.all.new_secondarycontactemail2 != null &&
            window.opener.document.crmForm.all.new_secondarycontactemail2.DataValue != null) {
        emailObj = new Object();
        emailObj['type'] = '9206';
        emailObj['category'] = '3';
        emailObj['data'] = window.opener.document.crmForm.all.new_secondarycontactemail2.DataValue;
        emailObj['name'] = window.opener.document.crmForm.all.new_secondarycontactemail2.DataValue;
        ar.push(emailObj);
    }

    crmForm.all.cc.DataValue = ar;

}

Lastly, publish the customization.  When you click "Send Email" from the contact form your addresses will be added to the CC. 

 

 

Plugin Implementation

The same functionality can be added using a plugin.  The benefits of using a plugin versus a client side implementation is that you can implement consistent functionality regardless of whether the email send is triggered from a client portal, another plugin, or through the CRM UI.  The JavaScript implemenation is useful in that the user can see who the email is being sent to before they click Send. 

Install the plugin using the registration tool as a pre-create step for email save.  This will add the CC on the initial save of the email.  It is triggered regardless of whether the user clicks save or send from the email form.  The code also includes duplicate checking logic. So, if the user adds the email address to the CC from the form it will not duplicate the same address in the plugin.

using System.Web;
using System.Net;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Sdk.Query;
using System.Reflection;
using System.Web.Services.Protocols;
using Microsoft.Crm.SdkTypeProxy.Metadata;
using Microsoft.Crm.Sdk.Metadata;
using Inetium.CrmPlugins;
using System.Collections;

namespace Inetium.CrmPlugins.Plugins
{
    public class CaseUnresolvedEmailPlugin : IPlugin
    {
        public void Execute(IPluginExecutionContext context)
        {
            ICrmService service = null;
            try
            {
                if (context.InputParameters.Properties.Contains("Target") &&
                    context.InputParameters.Properties["Target"] is DynamicEntity)
                {
                    DynamicEntity entityInput = context.InputParameters.Properties["Target"] as DynamicEntity;
                    if (entityInput.Properties.Contains("regardingobjectid"))
                    {
                        Lookup regardingObjectLookup = entityInput.Properties["regardingobjectid"] as Lookup;
                        service = context.CreateCrmService(true);
                       
                        // Only using this for contacts
                        if (regardingObjectLookup.type == EntityName.contact.ToString())
                        {
                            // Pull parent contact - using helper method
                            DynamicEntity ctn = CRMUtilities.RetrieveById(service, EntityName.contact.ToString(), "contactid", regardingObjectLookup.Value, new ColumnSet(new string[]{"new_secondarycontactemail1", "new_secondarycontactemail2"}));
                            if (!ctn.Properties.Contains("new_secondarycontactemail1") && !ctn.Properties.Contains("new_secondarycontactemail2"))
                                return;

                            string email1 = ctn.Properties.Contains("new_secondarycontactemail1") ? ctn.Properties["new_secondarycontactemail1"].ToString() : "";
                            string email2 = ctn.Properties.Contains("new_secondarycontactemail2") ? ctn.Properties["new_secondarycontactemail2"].ToString() : "";

                            DynamicEntity[] partyArrayExisting = entityInput.Properties.Contains("cc") ? entityInput.Properties["cc"] as DynamicEntity[] : new DynamicEntity[] { };
                            List<DynamicEntity> partyList = new List<DynamicEntity>(partyArrayExisting);

                            if (email1 != "" && !IsEmailAlreadyInCC(email1, partyList))
                            {
                                DynamicEntity party = new DynamicEntity();
                                party.Name = EntityName.activityparty.ToString();
                                party.Properties["addressused"] = email1;
                                partyList.Add(party);
                            }

                            if (email2 != "" && !IsEmailAlreadyInCC(email2, partyList))
                            {
                                DynamicEntity party = new DynamicEntity();
                                party.Name = EntityName.activityparty.ToString();
                                party.Properties["addressused"] = email2;
                                partyList.Add(party);
                            }

                            // Generate CC's list by adding in those from contacts
                            if (partyList.Count > 0)
                                entityInput.Properties["cc"] = partyList.ToArray();

                        }

                    }
                }
            }
            catch (SoapException se)
            {
                throw new Exception(se.Detail.InnerText);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            finally
            {
                if (service != null)
                {
                    service.Dispose();
                    service = null;
                }
            }
        }

        private bool IsEmailAlreadyInCC(string email, List<DynamicEntity> activityPartyList)
        {
            bool bAlreadyExists = false;
            foreach (DynamicEntity existingParty in activityPartyList)
            {
                // compare to existing CCs
                if (existingParty.Properties.Contains("addressused") &&
                    (existingParty.Properties["addressused"].ToString() == email))
                {
                    bAlreadyExists = true;
                    break;
                }
            }
            return bAlreadyExists;
        }
    }
}

Source: Andrew Zimmer

Tags: , , , ,

ASP.Net | CRM | MSCRM | mscrm4

CRM 4 Install and SQL Error 15401

by Phil Adams 4. May 2010 13:09

Last week i have had an issue where during the install of CRM 4, this is a fresh install the setup as follows:

SQL Box - Windows 2008 R2 + SQL2008 SP1
CRM Box - Windows 2008 R2

now both boxes have been freshly prepped and joined to the domain, all with out errors.
for added installation proof both servers had their windows firewalls turned off.

upon the Installation of CRM i encountered this error:

14:41:37|  Error| System.Exception: Action Microsoft.Crm.Setup.Server.GrantConfigDBDatabaseAccessAction failed. ---> System.Data.SqlClient.SqlException: Windows NT user or group 'Domain\SQLAccessGroup {9e798758-54f6-44a6-93a6-51b6faf49928}' not found. Check the name again.

   at Microsoft.Crm.Setup.Database.SharedDatabaseUtility.GrantDBAccess(String sqlServerName, String databaseName, String groupName, CrmDBConnectionType connectionType)

   at Microsoft.Crm.Setup.Server.GrantConfigDBDatabaseAccessAction.Do(IDictionary parameters)

   at Microsoft.Crm.Setup.Common.Action.ExecuteAction(Action action, IDictionary parameters, Boolean undo)

   --- End of inner exception stack trace ---, Error, RetryCancel, Option1

Now its quite obvious at this stage that it is failing to Gannt Access to an account to the MSCRM_CONFIG Database on the SQL server.

so i tried to add the SQLAccessGroup manually in the SQL Management Tools and the Error 15401: Windows NT user or group 'SCDC2003\SQLAccessGroup {9e798758-54f6-44a6-93a6-51b6faf49928}' not found. Check the name again.

now this is strange as i could not add the account manually, So i thought is this just Group Related and tried add a domain user and the same problem reared it's ugly head.

so a call to the Microsoft Support Team and some tests later, after running:

name2sid.zip (13.04 kb)

via the command line:
name2sid.exe domain\anyuser

the real error appeared:
LookupAccountName failed with error: The trust relationship between this workstation and the primary domain failed. (0x6fd/1789)

Now this helps the Microsoft Guys a lot so they asked me run:
netdom resetpwd /server:<DomainControllerName> /userd:<domain\domainadminuser> /passwordd:<password>

This Command Replied with:
The machine account password for the local machine has been successfully reset.

The command completed successfully.

now the next thing to do is to put this new command into action by restarting the netlogon service:
net stop netlogon
net start netlogon

and clear the kerberos Tickets on the sql server:
klist purge

then i tried adding the group manually again and hey presto and shazzam, it was working.

i hope this helps others having similar problems.

Phil

Tags: , , ,

ASP.Net | CRM | CRM Plugin | Group Policy | MSCRM | mscrm4

Debugging Workflows and Plugins in Microsoft Dynamics CRM 4.0

by Phil Adams 30. April 2010 00:21

The concept of debugging plugins and workflows has been blogged about in the past.  There is a lot of great information out there.  One thing I have noticed about debugging is that it involves a lot of steps.  These steps take time.  You end up waiting forever from the time you make a code change to the time that you are debugging again.  I wanted to share a couple things I have learned about debugging that have saved me a great deal of time.

Time Wasters:

  • Starting and stopping IIS and the async service
  • Deploying your new assembly
  • Attaching to processes
  • Getting back to debugging after making a small code change

Let’s get started… …

Project Setup
I like to start with a vanilla system on a virtual PC (VPC) that has CRM installed.  I import the customizations into my environment and set up some quick test data.

Note:  I stay away from remote debugging when possible as it requires specific security privileges on the CRM server and you will affect users that are trying to access the system.

From my VPC I open the project that includes my workflow or plugin.  Be sure to set the build path to the bin/assembly folder of your CRM instance.  On my machine it is C:\Program Files\Microsoft Dynamics CRM\server\bin\assembly\.  It varies from installation to installation.   Building to this location allows us to register the plugin to disk and make code changes quickly without having to move files around or re-register anything.

Register Plugin
Open the registration tool and register your plugin or workflow.  Be sure to register the plugin to disk.

Note:  When you move to production I recommend registering to the database, but registering to disk works great for debugging.

Attaching to Processes
Now that you have the plugin registered, you are ready to start debugging.  You need to attach to W3WP.exe to attach to plugins since they run within IIS.  To attach to workflows you need to attach to the CRM async service which is Crmasyncservice.exe.

 
Select w3wp.exe and Crmasyncservice.exe and click “Attach.”

To expedite the process I used a trick from Janne Mattila.  You leverage VS macros to attach to the processes by using a shortcut key. I have Ctrl+Shift+V tied to a script that attaches to both w3wp and crmasyncservice.   See my script below.

Courtesy of Janne Mattila:  http://blogs.msdn.com/jannemattila/archive/2008/10/30/attaching-debugger-to-w3wp-exe-using-nice-and-easy-keyboard-shortcut.aspx

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics

Public Module AttachHelper
    ' This subroutine attaches to w3wp.exe:
    Sub Attach()
        Dim attached As Boolean = False
        Dim proc As EnvDTE.Process

        For Each proc In DTE.Debugger.LocalProcesses
            If (Right(proc.Name, 8) = "w3wp.exe") Then
                proc.Attach()
                attached = True
            End If
        Next

        If attached = False Then
            MsgBox("Couldn't find w3wp.exe")
        End If

        attached = False
        For Each proc In DTE.Debugger.LocalProcesses
            If (Right(proc.Name, 19) = "CrmAsyncService.exe") Then
                proc.Attach()
                attached = True
            End If
        Next
        If attached = False Then
            MsgBox("Couldn't find crmasyncservice.exe")
        End If
    End Sub
End Module

So we are now able to quickly attach to processes.  We can debug.  Awesome!!!

 

But wait!  Next, you discover you need to make a code change.  We don’t have edit-and-continue available in plugins yet.  So, you stop the your session, make your code change and you get the following error when you try to build.

 

This is because you are trying to build to a file location that is already locked by the async service and/or IIS.  To get around this I have a bat file on my desktop and I run it every time I want to rebuild.  It includes the following:
iisreset
net stop MSCRMAsyncService
net start MSCRMAsyncService
"C:\Program Files\Internet Explorer\iexplore.exe" http://andrewvpc:5555

This script restarts IIS to unlock w3wp.exe.  Second, it restarts the async service.  Lastly, I reopen the CRM website in IE, so IIS will re-spawn itself (replace andrewvpc with your servername).  Otherwise, when you try to attach to processes it won’t be able to find w3wp.exe.

 

Now that you have ran the script, try to rebuild.  You will be rid of your error message and it will build successfully.  Now re-run your macro using Ctrl+Shift+V.  You are back to debugging again. Enjoy!!!

Source: Andrew Zimmer

Tags: , ,

CRM | CRM Plugin | MSCRM | mscrm4

Date field lookup cut off in IE8

by Phil Adams 29. April 2010 12:25

This seemed strange to me, as I knew that I had used right hand date fields in CRM 4 previously, and it had worked.  I tested on another machine, this time with IE 7, and the calendar lookup was not cut off.

IE 7    IE 8

Internet Explorer 7                                          Internet Explorer 8

Turns out that it’s not really a bug in CRM, but rather a side effect from some changes in Internet Explorer 8.  The CRM team worked with the IE team and came back with a fix for this issue.  The fix is to install IE update 974455 and make the following registry change on the client:

HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Main\FeatureControl
Create a new Key, and name it as follows:
FEATURE_FORCE_POPUPS_ONTO_MONITOR_KB974537
Within this key, point to New , and then click DWORD Value
Type iexplore.exe for the name of the key
Right-click iexplore.exe , click Modify and then type 1 in the Value data field

Read more about this fix at the UK MSCRM Support Blog.

Tags: , , , , ,

CRM | IE8 | Javascript | MSCRM | mscrm4

Determining CRM 4 ServicePack Level

by Phil Adams 30. March 2010 11:39

Since Microsoft first released Microsoft CRM 4.0 (in early 2008) they have released several hotfix roll ups.  Each hotfix rollup fixes several problems that have been identified since the initial release.  It is generally recommended that you keep your CRM patched with the latest release.  However, it can often be difficult to know which hotfix has been applied to an installation of Microsoft CRM. 

To determine which hotfix has been applied to your installation look at Help About (found on the top right hand side of the Internet Explorer window if using CRM via the browser or CRM > Help > About if using CRM via Outlook.  The Help About window will look similar to the following:

Help About in Microsoft Dynamics CRM 4

The hotfix that has been applied to your installation is indicated by the number in brackets after the CRM 4.0, and in particular the final four digits. An explanation of the numbers and their corresponding release numbers is given below:

RTM: 4.0.7333.3

Rollup 1: 4.0.7333.1213

Rollup 2: 4.0.7333.1312

Rollup 3: 4.0.7333.1408

Rollup 4: 4.0.7333.1551

Rollup 5: 4.0.7333.1644

Rollup 6: 4.0.7333.1750

Rollup 7: 4.0.7333.2138

Rollup 8: 4.0.7333.2542

Rollup 9: 4.0.7333.2644

Rollup 10: 4.0.7333.2741

Rollup 11: 4.0.7333.2861

Rollup 12: 4.0.7333.2935


One reason that it is important to be able to determine the hotfix version of an installation is when you want to import customisations from one version to another.  This is not always possible if the two installations are running different hotfixes.

Another reason to upgrade your installation is to benefit from the latest security patches and optimisation improvements.  In particular, hotfix rollup 5 introduced a number of speed improvements.

Tags: , ,

CRM | MSCRM | mscrm4

Microsoft Dynamics CRM Team Blog : Integrating Microsoft Dynamics CRM via App Fabric

by Phil Adams 12. February 2010 11:01

 

Integrating Microsoft Dynamics CRM via App Fabric

I am excited about the Microsoft’s Azure initiative. App Fabric is a part of Window Azure platform and as indicated at http://www.microsoft.com/windowsazure, makes it simpler to connect cloud and on-premise applications. It also simplifies the on-premise to on-premise application connection separated by firewalls and enables management of such interaction via the configurable rules of Access Control Service (ACS).

Consider a simple application which syncs accounts created in CRM system with another system. This application works in a classical polling model, where it polls CRM every so often for any newly added accounts. The polling interval may differ based on the other system requirements on how current the data needs to be represented. The higher the urgency, more the load it is going to put on CRM by repeatedly running the search query for new accounts.

It would be nice if the syncing module had some mechanism of being notified if a new account has been created, classical push model, which will allow it to only query CRM if needed and better if it can be notified with all the account details it needs to sync between the two systems. Lets try to see how we can alleviate the need of continuous polling with still maintaining the same data consistency between the two systems.

Pre-requisites

We have a functional CRM 4.0 system with App Fabric SDK installed, an account with the Windows Azure platform App Fabric and a program that polls CRM via the SDK to sync data every, say 15 minutes.

Our goal is to enhance the polling code to run search query against CRM only when new accounts are created and cut down on blind polling behavior.

To achieve our goals, we will write a plug-in in CRM that would post to the App Fabric any time a new account is created, The endpoint to which the data is posted by CRM is maintained by the application and it will track when was the last account in CRM created on.

Plug-in code

Code Snippet
  1. using System;
  2. using System.Globalization;
  3. using System.IO;
  4. using System.Text;
  5. using System.Xml;
  6. using System.ServiceModel;
  7.  
  8. using Microsoft.Crm.Sdk;
  9. using Microsoft.ServiceBus;
  10.  
  11. namespace AppFabricPlugin
  12. {
  13.     public class Plugin : IPlugin
  14.     {
  15.         // Variables
  16.         private string SolutionName;
  17.         private string ServicePath;
  18.         private string MgmtKey;
  19.  
  20.         #region Constructor
  21.         public Plugin(string config)
  22.         {
  23.             if (String.IsNullOrEmpty(config))
  24.             {
  25.                 throw new InvalidPluginExecutionException("config can not null or empty.");
  26.             }
  27.  
  28.             // Parse config.
  29.             string[] parts = config.Split(new char[] { ';' });
  30.             if (parts.Length == 3)
  31.             {
  32.                 SolutionName = parts[0];
  33.                 ServicePath = parts[1];
  34.                 MgmtKey = parts[2];
  35.             }
  36.             else
  37.             {
  38.                 throw new InvalidPluginExecutionException("Invalid config.");
  39.             }
  40.         }
  41.         #endregion
  42.  
  43.         #region IPlugin Members
  44.         public void Execute(IPluginExecutionContext context)
  45.         {
  46.             // Set mode to http (https).
  47.             ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Http;
  48.  
  49.             // Address.
  50.             Uri serviceUri = ServiceBusEnvironment.CreateServiceUri(Uri.UriSchemeHttps, SolutionName, ServicePath);
  51.  
  52.             // Binding
  53.             WS2007HttpRelayBinding binding = new WS2007HttpRelayBinding();
  54.             binding.Security.Mode = EndToEndSecurityMode.Transport;
  55.  
  56.             // Create the channel factory.
  57.             using (ChannelFactory<IServiceBusChannel> channelFactory = new ChannelFactory<IServiceBusChannel>(binding, new EndpointAddress(serviceUri)))
  58.             {
  59.                 // Apply the auth behavior
  60.                 channelFactory.Endpoint.Behaviors.Add(RetrieveAuthBehavior());
  61.  
  62.                 // Create and open the client channel
  63.                 using (IServiceBusChannel channel = channelFactory.CreateChannel())
  64.                 {
  65.                     channel.Open();
  66.                     // Use context correlationupdatetime to track the operation time.
  67.                     channel.Execute(context.PrimaryEntityName + ";" + context.CorrelationUpdatedTime.Value);
  68.                 }
  69.             }
  70.         }
  71.         #endregion
  72.  
  73.         #region Private members
  74.         private TransportClientEndpointBehavior RetrieveAuthBehavior()
  75.         {
  76.             // Behavior
  77.             TransportClientEndpointBehavior behavior = new TransportClientEndpointBehavior();
  78.             behavior.CredentialType = TransportClientCredentialType.SharedSecret;
  79.             behavior.Credentials.SharedSecret.IssuerName = "owner";
  80.             behavior.Credentials.SharedSecret.IssuerSecret = MgmtKey;
  81.  
  82.             return behavior;
  83.         }
  84.         #endregion
  85.  
  86.         #region Contracts
  87.         [ServiceContract(Namespace = "http://schemas.microsoft.com/crm/2007/Contracts")]
  88.         public interface IRemotePluginContract
  89.         {
  90.             [OperationContract(IsOneWay = true)]
  91.             void Execute(string data);
  92.         }
  93.  
  94.         public interface IServiceBusChannel : IRemotePluginContract, IClientChannel { }
  95.         #endregion
  96.     }
  97. }

Service listener code

Code Snippet
  1. public void Main(string[] args)
  2. {
  3.     // Set mode to http (https).
  4.     ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.Http;
  5.  
  6.     // Address.
  7.     Uri serviceUri = ServiceBusEnvironment.CreateServiceUri(Uri.UriSchemeHttps, SolutionName, ServicePath);
  8.  
  9.     // Binding
  10.     WS2007HttpRelayBinding binding = new WS2007HttpRelayBinding();
  11.     binding.Security.Mode = EndToEndSecurityMode.Transport;
  12.  
  13.     using (ServiceHost host = new ServiceHost(typeof(RemoteService)))
  14.     {
  15.         host.AddServiceEndpoint(typeof(IRemotePluginContract), binding, serviceUri);
  16.                                 host.Description.Endpoints[0].Behaviors.Add(RetrieveAuthBehavior());
  17.  
  18.         host.Open();
  19.  
  20.         Console.WriteLine("Press [Enter] to exit");
  21.         Console.ReadLine();
  22.     }
  23. }
  24.  
  25. [ServiceBehavior]
  26. public class RemoteService : IRemotePluginContract
  27. {
  28.     #region IRemotePluginContract Members
  29.     public void Execute(string data)
  30.     {
  31.         // Do something.
  32.     }
  33.     #endregion
  34. }

Plug-in registration

Compile and register the plug-in on account create. Register it to run asynchronously so that the post can happen independent of the actual create. In the config part add a semicolon separated string containing the solutionname;solutionpath;sharedkey

Configuring the ACS

We don’t need any rules configured in the ACS as we are using shared secret key to authenticate. All you would need to have is a solution name space on the App Fabric and use the management key. I have provided some links for you to get started.

With the above system in-place, the syncing module only needs to query CRM if the last account created on time is greater than then last time it synced. The model can be further improved and tweaked based to the requirements, but this simple demo enables us to see the integration empowerment that Windows Azure platform App Fabric enables. The best thing I like is it enables management of data syncing between the two systems.

References

If you are new to Windows Azure: http://www.microsoft.com/windowsazure/

If need more info on App Fabric: http://www.microsoft.com/windowsazure/appfabric

If you to download App Fabric SDK: http://www.microsoft.com/downloads/details.aspx?FamilyID=39856a03-1490-4283-908f-c8bf0bfad8a5&displaylang=en

If you need more info on CRM plug-in registration tool:

Cheers,

Shashi Ranjan

Microsoft Dynamics CRM Team Blog : Integrating Microsoft Dynamics CRM via App Fabric

Tags:

CRM | CRM Plugin | MSCRM | mscrm4

Generics with CRM

by Phil Adams 11. January 2010 23:26

 

Generics is a very nice feature of the C# and VB.NET language, and can be used to simplify the CRM Web Service method calls. One of my responsibilities is to develop a framework for CRM development, and one of the main classes in this framework is called CrmSystem, it wraps the CrmService methods among other things. Using generics one can then type,

C#
account acc = CrmSystem.Retrieve<account>(myAccountId);

VB
Dim acc As account = CrmSystem.Retrieve(Of account)(myAccountId)

also we use a special NameValue class CrmConditions to one of our Execute overloads, here is how it can look

C#
List<account> acc = CrmSystem.Execute<account>(new CrmCondition("emailaddress1", me@someone.com));

VB
Dim acc As List(Of account) = CrmSystem.Execute(Of account)(New CrmCondition("emailaddress1", "me@someone.com"))

This would retrieve all accounts that got the email "me@someone.com"

Here is one of the overloads for the Retrive Method that uses Generics, it calls an other overload that does the actual call to CRM Web Service using the EntityName string that we get thru typeof(T).Name

C#
public T Retrieve<T>(Guid id, params string[] columnSet) where T : BusinessEntity {
     return (T)Retrieve(typeof(T).Name, id, new ColumnSet(columnSet));
}

VB
Public Function Retrieve(Of T As BusinessEntity)(ByVal id As Guid, ByVal ParamArray columnSet As String()) As T
    Return DirectCast(Retrieve(GetType(T).Name, id, New ColumnSet(columnSet)), T)
End Function

Hope this gives some inspiration for your own CrmApi wrappers :)

Generics with CRM

Tags:

CRM | Custom Controls | FetchXML | MSCRM | mscrm4

MS CRM 4 Multi-layer Menu is not aligned properly in IE 8

by Phil Adams 26. November 2009 15:09

*** UPDATE: Rollup 9 FIXES THIS ISSUE, PLEASE UPGRADE TO UR9 ***

Today in the building i had someone ask me about this issue, now i had this issue myself for sometime and thought it was just my machine, well i am a developer and you know how we love to cause issues for ourselves.

now as your aware menus in CRM can have sub-menu items such as shown in the image below
Menu_Normal

BUT, for some reason mine and my colleagues menus were like this:
Menu_Overlaid

Now as you can see the sub-menu has overlaid itself on the root menu, now if i was trying to get to the “Send Shortcut” Node i couldn’t., now some menus this is still possible to get to such as:
Menu_Overlaid_Accessible

So the Journey had begin to find the solution, alas Google didn’t turn up anything except others with the same issue
Now all i knew was this is due to some IE8 setting as it seemed to work fine in the IE7 clients.

so i started off by resetting my browser settings to default, reloaded CRM and hey presto it worked!, Now being a developer was not satisfied with that solution, so the first thing was to add the CRM site back to the “Trusted Sites” list and BANG.. problem appeared again.

So now i new it must be something in the Zone settings of IE8, so painfully went through each setting until the problem disappeared.

“YER GET ON WITH IT” i hear you say.
well the setting is “Allow script-initiated windows without size or position constraints” was set to Disable.

Enable this Setting and the issue disappears, Now i have to say at this point am at a loss, because as with most applications there is a lot of settings but NO HELP as to what they do or impact…

jeez, since when have we started buying products without comprehensive manuals?

Anyway rant over, problem fixed and on to the next one.

Tags: , , , ,

CRM | IE8 | MSCRM | mscrm4

CRM Date and Time Conversions

by Phil Adams 28. October 2009 11:04

using System;
using Microsoft.Crm.Sdk;

/// <summary>
/// Converts Crm types to and from system types.  
/// </summary>
/// <remarks>
/// TODO: Move into CRM Assembly.
/// </remarks>
public static class CrmConverter
{
     /// <summary>
     /// Converts CrmDateTime to Local DateTime.
     /// </summary>
     /// <param name="crmDateTime">The CRM date time.</param>
     /// <returns>A DateTime.</returns>
     public static DateTime ToDateTime(CrmDateTime crmDateTime)
     {
         return crmDateTime.UserTime;           
     }

     /// <summary>
     /// Converts CrmDateTime to UTC DateTime.
     /// </summary>
     /// <param name="crmDateTime">The CRM date time.</param>
     /// <returns>A DateTime.</returns>
     public static DateTime ToUtcDateTime(CrmDateTime crmDateTime)
     {
         return crmDateTime.UniversalTime;
     }

     /// <summary>
     /// Converts UTC DateTime to CrmDateTime.
     /// </summary>
     /// <param name="dateTime">The UTC date time.</param>
     /// <returns>An instance of the CrmDateTime class.</returns>
     public static CrmDateTime ToCrmDateTime(DateTime dateTime)
     {
         return new CrmDateTime(string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:s}Z", dateTime.ToUniversalTime()));
     }
}

Tags: , ,

CRM | MSCRM | mscrm4

Creating A Group Policy Administrative Template For Deploying The CRM Client AutoUpdate Registry Keys

by Phil Adams 21. August 2009 16:26

In my Article CRM v4 Client AutoUpdate Procedure, I talked about deploying the required registry keys:

HKEY_LOCAL_MACHINE\Software\Microsoft\MSCRMClient\AutoUpdateDownloadUrl (for 32bit)
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\MSCRMClient\AutoUpdateDownloadUrl (for 64bit)

This can either be done by tediously going to each PC and manually creating them or executing a .reg Merge file.
But that would not be time constructive, im sure you have more pressing things to do.

SO, im going to show you haw to create a Group Policy Administrative Template so you can deploy the registry entries the next time the pc reboots or refreshes it domain policy., right here goes:

;------------------------------- Start Of FILE --------------------------------------------

CLASS MACHINE
CATEGORY CRM_Client_AutoUpdate
  POLICY Update_Share_32Bit
  EXPLAIN !!ClientHelp
  KEYNAME Software\Microsoft\MSCRMClient
    PART "Set the File Share to :" EDITTEXT REQUIRED
    DEFAULT !!DefaultServer
    VALUENAME "AutoUpdateDownloadUrl"
    END PART
  END POLICY

  POLICY Update_Share_64Bit
  EXPLAIN !!ClientHelp
  KEYNAME SOFTWARE\Wow6432Node\Microsoft\MSCRMClient
    PART "Set the File Share to :" EDITTEXT REQUIRED
    DEFAULT !!DefaultServer
    VALUENAME "AutoUpdateDownloadUrl"
    END PART
  END POLICY
END CATEGORY

[strings]
dummy="dummy"
DefaultServer="http://mscrm/crmpatches/"

;explains
ClientHelp="Set The File Share That The CRM Client For Outlook Checks To See If There Is An Update., NOTE: MAKE SURE YOU FINISH URL WITH '/' E.g. 'http://[servername]/crmpatches/'  By Phil Adams(Cambridge Online)"

;------------------------- End of File -------------------------------------------------

Save the Above to a file called “CRM AutoUpdate.adm”

Then load up GPMC, then create and link the new policy to the OU required.
open the new policy and under User Configuration , right-click Administrative Templates and select Add/Remove Administrative Templates.

find the new ADM file and highlight it, then select Add. It will be copied into the policy in SYSVOL automagically. 
Now we highlight Administrative Templates and select View | Filtering.
Uncheck "Only show policy settings that can be fully managed" (i.e. any custom policy). It will look like this:
img3Now if you navigate to your policy, you get this (see the cool explanation too? No one can say they don’t know what this policy is about!
img4

Tags: , , ,

CRM | MSCRM | mscrm4 | Group Policy

About Phil

Phil has been working with CRM since the BETA of CRM v1.0 and has seen a lot of the problems arising from installation, maintenance and Development.

Phil specializes in the ISV area of CRM; Creating Add-Ons and Plugins for various clients.

Phil currently works as a Microsoft Dynamics CRM Consultant and Developer for Cambridge Online Systems Ltd In the UK.

Phil also has several Microsoft Certifications in .NET and CRM