Kids, don't try this at home!

Tips & Tricks for C#, ASP.NET and CRM
Storing Custom Settings in Dynamics CRM 2011

While browsing the Dynamics CRM 2011 Development forum I came across a post asking where people store their settings for ISV extensions. The way I’ve done this is by creating a custom entity called “Settings” with 3 fields, Key, Value and Encrypted (yes/no).

Example:

Then we have a helper class that gets these values. Here’s an example

Inside a Plugin

IOrganizationService ios = factory.CreateOrganizationService(…);
Setting settings = new Setting(ios);
 
int defaultDays = settings.Get<int>("complaint.default.acknowledgementdeadlinedays");

The Settings.cs file

Couple of things to note, the EncryptDecrypt method is blanked out, you’ll need to implement your own encryption and decryption routine. The Get method only has a few known type castings, add more as required.

public class Setting
{
    private IOrganizationService _sdk = null;
    public Setting(IOrganizationService context)
    {
        _sdk = context;
    }
 
    #region encryption
    internal static string EncryptDecrypt(string textToEncrypt)
    {
        return textToEncrypt; // up to you to implement encryption
    }
    #endregion
 
    // some common type castings, you can add more if required
    public T Get<T>(string key)
    {
        string value = this[key];
        if (!string.IsNullOrEmpty(value))
        {
            if (typeof(T) == typeof(string))
            {
                return (T)Convert.ChangeType(value, typeof(string));
            }
 
            if (typeof(T) == typeof(int))
            {
                return (T)Convert.ChangeType(int.Parse(value), typeof(int));
            }
 
            if (typeof(T) == typeof(decimal))
            {
                return (T)Convert.ChangeType(decimal.Parse(value), typeof(decimal));
            }
 
            if (typeof(T) == typeof(DateTime))
            {
                return (T)Convert.ChangeType(DateTime.Parse(value), typeof(DateTime));
            }
        }
 
        return default(T);
    }
 
    #region settings storage
    private Dictionary<string, string> _values = new Dictionary<string, string>();
    private Dictionary<string, Guid> _cached = new Dictionary<string, Guid>();
 
    internal string this[string key]
    {
        get
        {
            if (_values == null || _values.Count == 0) { Refresh(); }
            if (_values.Keys.Contains(key))
            {
                return _values[key];
            }
 
            return "";
        }
        set
        {
        }
 
    }
    #endregion
 
    #region helper methods
    public void Refresh()
    {
        _cached.Clear();
 
        // get all the settings and parse it into a collection first
        QueryExpression qe = new QueryExpression("mag_setting") { ColumnSet = new ColumnSet(true) };
        var results = _sdk.RetrieveMultiple(qe);
 
        if (results != null && results.Entities != null && results.Entities.Count > 0)
        {
            results.Entities.ToList().ForEach(setting =>
            {
                var current = setting as Entity;
 
                Guid id = current.Get<Guid>("mag_settingid");
                bool encrypted = current.Get<bool>("mag_encrypted");
                string key = current.Get<string>("mag_key");
                string value = current.Get<string>("mag_value");
                if (encrypted) { value = EncryptDecrypt(value); } // decrypt if the setting is encrypted
 
                // make sure we don't double up on the settings, otherwise .net will throw an error
                if (!_cached.Keys.Contains(key)) { _cached.Add(key, id); }
                if (!_values.Keys.Contains(key)) { _values.Add(key, value); }
            });
        }
    }
    #endregion
}
Programmatically downloading images files from Dynamics CRM 2011

I came across an issue with an upgrade from CRM 4 to CRM 2011 where the images were all messed up, transparency was gone and had pixelated images, there were over 100 custom entities and download/uploading these manually was not an option.

Here’s a small bit of code to download image web resources from Dynamics CRM 2011.

private static void DownloadIcons(IOrganizationService sdk, string path)
{
    QueryExpression qe = new QueryExpression("webresource");
    qe.ColumnSet = new ColumnSet("name", "webresourcetype", "content", "displayname");
 
    // 7 = gif, 10 = ico
    qe.Criteria.AddCondition("webresourcetype", ConditionOperator.In, new int[] { 7, 10 });
 
    Console.WriteLine("downloading, please wait...");
 
    var results = sdk.RetrieveMultiple(qe);
    var all = results.Entities.ToList();
 
    Console.WriteLine("found: {0} web resources", all.Count);
 
    if (!Directory.Exists(path)) { Directory.CreateDirectory(path); }
 
    all.ForEach(a =>
    {
        var type = a.GetAttributeValue<OptionSetValue>("webresourcetype");
        string name = a.GetAttributeValue<string>("name") + ((type.Value == 7) ? ".gif" : ".ico");
 
        // remove any simulated paths using the schema name, eg: mag_/img/xyz.extension
        if (name.IndexOf("/") > -1)
        {
            name = name.Substring(name.LastIndexOf("/") + 1);
        }
 
        var content = Convert.FromBase64String(a.GetAttributeValue<string>("content"));
        File.WriteAllBytes(Path.Combine(path, name), content);
 
        Console.WriteLine("downloaded: {0}", name);
    });
 
    Console.WriteLine("all done!");
}
Code Generating Code…programmatically retrieve N:N relationships

I recently needed to write a plugin that associated multiple different record types with a custom entity. The custom entity contained over 30 system many to many relationships with the other record types. 

There are couple of different approaches. One would’ve been to dynamically retrieve the relationship when the plugin ran, this means an extra call to the CRM service. The other option was to have a static dictionary mapping inside the plugin code. I chose the 2nd option because the relationships would not change and there is no need to make an additional call to the CRM service.

Now the time constraining task of copy pasting attributes and schema names from the customizations area. 30-45 seconds per copy paste would have taken around 15-20mins but there’s a better less error prone way.

Write a small console application to retrieve the N:N relationships for the entity and write out to te debug window and copy paste :)

Here’s the code, enjoy!

IOrganizationService sdk = null; // connect to crm here
 
RetrieveEntityRequest request = new RetrieveEntityRequest();
request.RetrieveAsIfPublished = true;
request.LogicalName = "...your entity name here...";
request.EntityFilters = EntityFilters.Relationships;
 
RetrieveEntityResponse response = (RetrieveEntityResponse)sdk.Execute(request);
EntityMetadata metadata = response.EntityMetadata;
 
if (metadata != null && metadata.ManyToManyRelationships != null)
{
    metadata.ManyToManyRelationships.ToList().ForEach(nn =>
    {
        System.Diagnostics.Debug.WriteLine("");
    });
}
Automated Deployment Tools for CRM 2011 – Import Solution with Progress

We use a lot of automated tools to deploy solutions to our test, UAT and production environments. One of the tools allows us to rapidly deploy a solution into multiple environments. For example, extracting a managed solution from DEV to TEST then to UAT, once UAT has passed we need to deploy up to 20 Dynamics CRM Online instances, doing this manually is too time consuming…

The tool looks at an xml file which specifies the connection settings for multiple CRM organizations, then when it’s run it’ll import and report the progress.

Here’s the code (stripped down to only include the import and progress reporting)

private static void ImportSolution(IOrganizationService sdk, string solutionPath)
{
    byte[] data = File.ReadAllBytes(solutionPath);
    Guid importId = Guid.NewGuid();
 
    ImportSolutionRequest request = new ImportSolutionRequest()
    {
        CustomizationFile = data,
        ImportJobId = importId
    };
 
    Thread t = new Thread(new ParameterizedThreadStart(ProgressReport));
    t.Start(importId);
 
    sdk.Execute(request);
    Console.WriteLine("imported");
}

First we have to create a new ImportSolutionRequest, the important thing to note here is that you need to specify an ImportJobId, this allows us to track the progress.

To report the progress

private static void ProgressReport(object importId)
{
    // connect to crm again, don't reuse the connection that's used to import
    IOrganizationService sdk = null;
    try
    {
        var job = sdk.Retrieve("importjob", (Guid)importId, new ColumnSet("solutionname", "progress"));
        decimal progress = Convert.ToDecimal(job["progress"]);
 
        Console.WriteLine("{0:N0}%", progress);
 
        if (progress == 100) { return; }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
 
    Thread.Sleep(2000);
    ProgressReport(importId);
}

The ProgressReport function queries the importjob entity with the importId we specified just before starting the solution import.

Enjoy!

CRM 4 Upgrade to CRM 2011 - Blank Type Name in Plugin Registration Tool

After an upgrade of CRM 4 to CRM 2011 the plugin registration tool will sometimes show (Plugin) instead of the actual plugin type name.

The reason is because of the PluginTypeBase table the Name field is blank. Here’s an unsupported way of copying back the TypeName to the Name field.

  1. Go into the SQL Server
  2. Open up SQL Management Studio
  3. Run the query below on your *_MSCRM database
UPDATE PluginTypeBase
    SET Name = TypeName
WHERE Name IS NULL
 
Large CRM 4 to CRM 2011 Upgrade – Too many workflows…

While in the process of upgrading a large Dynamics CRM 4 to CRM 2011 which has over 80 workflows! Some of which are very complex with custom workflow activities, I needed to quickly identify which of those had custom activity steps.

To get a list of workflows with custom activities do the following

  1. Go into the SQL server
  2. Open SQL Management Studio
  3. Run the query below against your *_MSCRM database
SELECT name, xaml
    FROM Workflow
WHERE Xaml like '%AssemblyQualifiedName=%'
ORDER BY name
CRM 2011 highlight & copy from a grid view

One of the annoying things with CRM 2011 is the inability to copy paste data from a list, the ability to highlight a record or cell has been disabled, I’m sure there was a good reason but we need this functionality.

Here is a work around if your deployment is on-premise.

  • Open up the CRMWeb\_static\_common\scripts\global.js file in a text editor
     
  • Find this line of code (line # 6902)
    Mscrm.GlobalEvents.$4Z_0 = function Mscrm_GlobalEvents$$4Z_0() {

    You’ll notice that there are a bunch of if statements checking to see if you’re trying to highlight a specific element, so what we want to do now is to make the smallest possible change to bypass the check.

  • Run your eye across the if statements until you find ‘ms-crm-Field-Data-Print

  • Change it to ‘ms-crm-List-DataCell’, what we’ve just done is excluded cells of grids and enabled highlighting.

Enjoy!

Automatically share a personal view with another user

 

Recently we needed to share personal views with a system/service user to draw Bing maps. (keep an eye out for a cool blog post about geo mapping and geo boundary integration with advanced find views).

Advanced Find views are stored under the userquery entity; unfortunately this entity is marked as private by CRM, meaning we’re not able to register plugins, there is an unsupported work around though.

If you open up the SdkMessageFilter table and set IsCustomProcessingStepAllowed to 1 on the userquery entity you’ll be able to register plugins under the userquery entity.

Next step is to create the plugin and write some code to share this advanced find view with another user, to do this programmatically use the code below.

GrantAccessRequest request = new GrantAccessRequest
{
    PrincipalAccess = new PrincipalAccess
    {
        AccessMask = AccessRights.ReadAccess,
        Principal = new SecurityPrincipal
        {
            PrincipalId = userId,
            Type = SecurityPrincipalType.User
        }
    },
    Target = new TargetOwnedDynamic 
    { 
        EntityId = queryId, EntityName = "userquery" 
    }
};

To get the queryId use the Input/OutputParameters of the IPluginExecutionContext, to view other parameters that are passed in via the IPluginExecutionContext use our mSpy tool (http://mspy.codeplex.com), it takes the guess work out of writing plugins!

Posted: Dec 09 2010, 07:33 PM by gperera | with 1 comment(s)
Filed under:
Putting a clock to the face in a Dynamics CRM Record

This is an enhancement to the blog post that was written last week about how to put a face to the name in a Dynamics CRM record. As you can see from the above image we also added the option to see the local time of a contact in CRM.

If you’re working with people around the globe it’s useful to know what time it’s on their side of the world so you don’t wake them up at 2am in the morning doing a sales call!

How does it work?

Very simple, using jquery we make a call to Google (sorry Bing, tried to use your services but you didn’t like me asking ‘time in xyz’) then parse the html and show the result.

Posted: Dec 06 2010, 09:02 PM by gperera | with no comments |
Filed under:
Putting a Face to the Name in a Dynamics CRM Record

 

We recently added a neat little customization to a Microsoft Dynamics CRM 4 organization so that when you open a contact record it goes off to LinkedIn, finds the persons picture if there is one and displays it on the contact record.

It’s very similar to the Social Connector in Outlook, if you’d like this customization in your environment contact me or if you’re a developer here’s how you can write it from scratch.

LinkedIn Gotcha

LinkedIn has an API which uses OAuth but it requires the user to login to LinkedIn and get redirected back which renders this kind of application to application communication useless. One interesting thing to note is that the Outlook Social Connector works without user interaction because it was written by LinkedIn and uses some backdoor into their system to match contacts via email addresses.

Luckily there is a public search - http://www.linkedin.com/pub/dir/?first={0}&last={1}, if you pass a person’s first and last names it’ll return a list of matching records, unfortunately there’s no way to pass the company name to the public search.

Once you have the raw html from the public search results page, simply parse the html, create a little algorithm to match the company and return the best matching result.

You can try it out here http://magic.magnetism.co.nz/linkedin.htm

Enjoy!

Posted: Dec 02 2010, 09:07 PM by gperera | with no comments |
Filed under:
More Posts Next page »