December 2, 2015

India and Canada Data Center Discovery URLs

In the last months Microsoft announced the opening of new data centers for Dynamics CRM Online in India and Canada.
These data centers are not available yet, but Microsoft defined the Discovery Web Service URLs. The addresses are: (India) (Canada)

The Indian data center is identified by the crm8 host, the Canadian one by crm3. This will affect also the organization url, for example if Contoso Ltd. has two branches, one in India and the other one in Canada, their urls will be:

The relative MSDN page has NOT been updated to include this change, the next table is a recap:

crmNorth America
crm2South America
crm9North America 2 (CRM Online for Government)

July 21, 2015

Visual Studio 2015 Shared Projects and CRM Plugins Development

Visual Studio 2015 is now available and inside the new features and improvements there is one that I like very much: Shared Projects. A Shared Project is intended to share easily the code between different platforms.
For C# projects this was already possible using (portable) class libraries and adding the assembly to the main project references.

Which is the big advantage of Shared Projects for a Dynamics CRM developer?
With Shared Projects the code is NOT compiled to a separate assembly but directly inside the main assembly of your project.

This is can be very useful for Plugin and Custom Workflow Activity development, because now we can create a common library with CRM methods to be used inside our plugins WITHOUT the need to use ILMerge for creating a single assembly in order to be registered inside Dynamics CRM.

As example I created a small shared project containing a simple method to check the current CRM Version:
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
namespace CRM.Common
    public static class Utils
        public enum CRMVersion
            Unknown = 0, CRM2011 = 2011, CRM2013 = 2013, CRM2015 = 2015

        public static CRMVersion GetCRMVersion(IOrganizationService service)
            RetrieveVersionRequest versionRequest = new RetrieveVersionRequest();
            RetrieveVersionResponse versionResponse = (RetrieveVersionResponse)service.Execute(versionRequest);
            string version = versionResponse.Version;
            if (version.StartsWith("5")) { return CRMVersion.CRM2011; }
            if (version.StartsWith("6")) { return CRMVersion.CRM2013; }
            if (version.StartsWith("7")) { return CRMVersion.CRM2015; }
            return CRMVersion.Unknown;
After I created a normal Class Library project for my plugin and I added the Shared Project to the solution and a reference inside the Class Library:

The plugin code is very simple, it throws an exception indicating the CRM version:
using Microsoft.Xrm.Sdk;
using System;
using static CRM.Common.Utils;

namespace CRM.MyPlugin
    public class MyPlugin : IPlugin
        public void Execute(IServiceProvider serviceProvider)
            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            CRMVersion version = GetCRMVersion(service);
            throw new InvalidPluginExecutionException(version.ToString());
If we compile the solution only a single assembly is generated and if we analyze it using ILSpy we can confirm that the Shared Project is compiled inside the main assembly:

And the plugin works without issues:

Shared Projects can be very useful for Dynamics CRM development because will definitely improve the quality and the reuse of the code.

Shared Projects are also available for Visual Studio 2013 as separate addon, you can download from here: Shared Project Reference Manager

July 14, 2015

New Dynamics CRM for phones iOS App and getFormFactor method

Yesterday Microsoft released the new iOS App for Dynamics CRM (iTunes link). The app only works with CRM versions 7.1 and higher, this means currently can be used only with CRM Online instances that received the Update 1.

The new App supports JavaScript so I wanted to test a little script with the method getFormFactor.

getFormFactor (MSDN link) is a new method introduced with CRM Online 2015 Update 1 returning a numeric value based on the current device. The possible values are:

ValueForm Factor

The script I wrote is:
function accountOnLoad() {
    var factor = Xrm.Page.context.client.getFormFactor();
    var message = "The Form Factor is: " + factor;
And the result is:

Note: the script is not called when a new record is created.

June 18, 2015

Hide fields dynamically based on Field Level Security privileges

When we set up Field Level Security for a field we can assign three different privileges (Read, Update, Write) to the users.
It is also possible to check the privilege using a supported JavaScript method: getUserPrivilege
This method returns an object with three boolean properties: canRead, canUpdate, canCreate.

We can use these properties in combination with the forEach method of attributes and controls collections in order to dynamically hide the fields.

The following code will hide dynamically all the fields protected by Field Level Security that the user can't read:
function HideSecuredFields() {
    // fetch all attributes on the form
        function(attribute, aIndex) {
            // get the FLS privileges
            var privileges = attribute.getUserPrivilege();
            // check if the user can't read the field
            if (privileges.canRead == false) {
                // fetch all the controls related to the attribute
                    function (control, cIndex) {
                       // hide the controls

May 11, 2015

Dynamics CRM Theme Generator

The Dynamics CRM 2015 Online Update 1 introduced a new feature called "Themes" to customize some aspects of the Dynamics CRM User Interface.
Because it's necessary every time to preview the changes in order to see how CRM will look like, I created a theme generator with a real time preview.
It's also possible to download the theme in order to be imported inside CRM.

The link is

April 27, 2015

GetAttributeValue demystified

Note: the content of this post is similar to this post from Dave Berry but I wanted to approach the same argument with some example code and tips.

Dynamics CRM allows to create fields with the following data types:

Except for "Single Line of Text" and "Multiple Lines of Text" (both use string), each one uses a different underline data type in .NET, some of these data types are nullable, some are not.

What means nullable and why we need to care about this?
Let's start with an example: In our CRM we have two records, the first record has all the fields filled with a value, in the second one all the fields are empty. When we use GetAttributeValue and there is a value, the method (fairly) returns the value. But what happens with our empty record? The response is "depends".

GetAttributeValue uses Generics, so we can choose to get a nullable type or not:
bool boolean = entity.GetAttributeValue<bool>("new_boolean");
bool? booleanNullable = entity.GetAttributeValue<bool?>("new_boolean");
In this case, if the value is null (for a Boolean/Two Options field means that no value is set) the first variable will contains false, the second will contains null.
The next table is a summary:

CRM Type .NET Type can hold null? default value
Single Line of Text string Yes
Option Set OptionSetValue Yes
Two Options bool No false
Image byte[] Yes
Whole Number int No 0
Floating Point Number double No 0.0
Decimal Number decimal No 0
Currency Money Yes
Multiple Lines of Text string Yes
Date and Time DateTime No DateTime.MinValue
Lookup EntityReference Yes
For the types that can hold null, we use GetAttributeValue and after check if it's null or not:
EntityReference lookupRef = entity.GetAttributeValue<EntityReference>("new_lookupid");
if (lookupRef == null) {
   // no value set
} else {
   // we have a value
For the types that can't hold null we need to ask ourselves: "The default value is enough for the requirement?"

If we are in a loop and we need to do a sum of an int field, the default value (0) is ok, so we can just do
int totalSum = 0;
foreach (Entity row in RowCollection.Entities) {
   int number = entity.GetAttributeValue<int>("new_wholenumber");
but if we are doing a multiplication we need to skip the null values, so we use int?
int totalMulty = 0;
foreach (Entity row in RowCollection.Entities) {
   int? number = entity.GetAttributeValue<int?>("new_wholenumber");
   if (number != null) {
otherwise with a null value our totalMulty variable will be 0.

DateTime is a particular case, it can't hold null but the default value (MinValue = 01/01/0001) can't be a valid CRM value (as happens with bool and numeric fields) so we can do the following check:
DateTime dateTime = entity.GetAttributeValue<DateTime>("new_datetime");
if (dateTime == DateTime.MinValue) {
   // no value set
} else {
   // we have a value
Practically when we don't use the nullable form, Dynamics CRM is doing the following:
double floating = entity.GetAttributeValue<double>("new_floating");
// equals to
double floating = entity.GetAttributeValue<double?>("new_floating").GetValueOrDefault();
The combination of the nullable form and the GetValueOrDefault can be useful in some scenarios. Let's say that we need to do a data migration to an external system, but if the source decimal Quantity is null, the target decimal Quantity must be -1.
decimal quantity = entity.GetAttributeValue<decimal?>("new_quantity").GetValueOrDefault(-1);
In this way we deal automatically the null values and they are ready for the target system.

Now you are a Dynamics CRM True Survivor!

April 2, 2015

Simplified Connection with complicated passwords

CRM 2011 introduced a very easy way to connect to Dynamics CRM instances: the Simplified Connection (MSDN:
Basically it's necessary to build a connection string instead of dealing with the specific deployment type (OnPremise, IFD or Online).

The downside of using a Simplified Connection is its weakness management of passwords containing special characters like double quotes, single quotes, ampersands.

Considering the MSDN example for a CRM Online connection:
Url=;; Password=passcode;
If the password is ;abc123 (note the semicolon) an exception will be thrown with the following message:
Format of the initialization string does not conform to specification starting at index 102. The solution for this problem is to include the password inside single quotes, the following connection string will work:
Url=;; Password=';abc123';
Assuming the connection string is builded dynamically the following code can be used:
string connectionString =
    String.Format("Url={0}; Username={1}; Password='{2}';", url, username, password);
What if our complicated password contains single quotes as well? Let's consider for example the following password: ;a''bc'123
In this case the previous exception (Format of the initialization string) will be thrown again. This issue can be solved "escaping" the single quotes using a Replace:
string connectionString =
    String.Format("Url={0}; Username={1}; Password='{2}';",
    url, username, password.Replace("'","''"));
Please note that the escape must be done also if your connection string is stored inside your app/web.config:
<add key="CRM"
value="Url=;; Password=';a''bc'123';"/>
But in this case our replace method will not work, because it will replace also the single quotes delimiting the password. In this scenario I suggest to put inside the app/web.config a placeholder instead of the delimiting single quotes that will be replaced after (for example #XYZ#):
<add key="CRM"
value="Url=;; Password=#XYZ#;a''bc'123#XYZ#;"/>
Then after the connection string is loaded we do the escape and the replace:
string connectionString = ConfigurationManager.ConnectionStrings["CRM"].ConnectionString;
// escape the single quotes inside the password
connectionString = connectionString.Replace("'","''");
// replace the placeholder with single quotes
connectionString = connectionString.Replace("#XYZ#","'");
Of course this will not work if the password contains the placeholder as well, so it's better to choose a long placeholder.

When the password is stored inside the app/web.config it's necessary to deal with another problem, the case that our password contains XML special characters (mostly double quotes) because this file is an XML.

If it's necessary to encode the password the following .NET method can be used:
string xmlPassword = System.Security.SecurityElement.Escape(password);
The result for the password ;a''b"c'123 (note the double quote between b and c that will create problems if not encoded) will be ;a&apos;&apos;bc&apos;123, a valid string to be written inside the app/web.config.

March 30, 2015

JavaScript OData Pagination with synchronous calls

One limit of the OData endpoint is that the response can only include up to 50 records, so if your result set has more records it's necessary to reiterate the request using the url inside the __next property contained inside the returned object.

The following code shows how to fetch the request using synchronous calls, because sometimes you need (or want) to block the user :)
function getODataRecords(ODataUrl) {

    // we return an object with a similar structure as the OData endpoint
    var allRecords = new Object();
    allRecords.results = new Array();
    // we loop until we have an url to query
    var queryUrl = ODataUrl;
    while(queryUrl != null) {

        // we build the request
        var ODataRequest = new XMLHttpRequest();"GET", queryUrl, false); // false = synchronous request
        ODataRequest.setRequestHeader("Accept", "application/json"); 
        ODataRequest.setRequestHeader("Content-Type", "application/json; charset=utf-8"); 

        if (ODataRequest.status === 200) {
            var parsedResults = JSON.parse(ODataRequest.responseText).d;
            if (parsedResults != null && parsedResults.results != null) {

                // we add the results to our object
                for (var i = 0; i < parsedResults.results.length; i++) {

                // check if there are more records and set the new url, otherwise we set to null the url
                if (parsedResults.__next != null) {
                    queryUrl = parsedResults.__next;
                } else {
                    queryUrl = null;
        } else {
            // if the request has errors we stop and return a null result
            queryUrl = null;
            allRecords = null;

    return allRecords;

// sample function to return all the accounts
function GetAllAccounts() {
    var serverUrl;
    if (Xrm.Page.context.getClientUrl !== undefined) {
        serverUrl = Xrm.Page.context.getClientUrl();
    } else {
        serverUrl = Xrm.Page.context.getServerUrl();
    var ODataPath = serverUrl + "/XRMServices/2011/OrganizationData.svc";
    var accountQueryUrl = ODataPath + "/AcccountSet?$select=AccountNumber,Name";

    // call our new method
    var retrievedAccounts = getODataRecords(accountQueryUrl);

    // alert each result
    if (retrievedAccounts != null) {
        for (var i = 0; i < retrievedAccounts.results.length; i++) {
            alert(retrievedAccounts.results[i].Name + " - " + retrievedAccounts.results[i].AccountNumber);

March 9, 2015

Japan and Australia Data Center Discovery URLs

Microsoft launched the new Data Center in Japan for CRM Online, you can read the announcement by Bob Stutz here:
Japan Datacenter Open for Business: Now Serving Japanese CRM Online Customers.

The Australia Data Center is also operative and available for companies from Australia, New Zealand and Fiji.

Microsoft defined also the Discovery Web Service URL of these data centers, the addresses are: (Japan) (Australia)

The Japanese data center is identified by the crm7 host, the Australian one by crm6. This will affect also the organization url, for example if Contoso Ltd. has two branches, one in Japan and the other one in Australia, their urls will be:

The relative MSDN page has been updated to include this change, the next table is a recap:

crmNorth America
crm2South America
crm9North America 2 (CRM Online for Government)

March 3, 2015

Moment.js and Dynamics CRM: Multi Language support

The current version of Dynamics CRM supports 45 languages (an excel file with the list can be downloaded here: mapped to LCID codes.
This LCID code is also the one returned by the following methods: Xrm.Page.context.getUserLcid and Xrm.Page.context.geOrgLcid (the language of the user and the base language of the organization).

Moment.js is a powerful library to manipulate dates in JavaScript and it has support for internationalization but accepts only a locale string ("en", "it", ...) and not the LCID value returned by the above methods. I created a small JavaScript library (crm_lang.js) that accepts the 45 LCID CRM codes in order to return their corresponding Moment.js locale strings. The library can be downloaded from Technet Gallery:

Moment.js is very useful when comes to manipulate dates (add or subtracts days) and more important for this sample to display the date in various formats:
var now = moment();
alert(now.format());                                // "2015-03-03T08:02:17-05:00" (ISO 8601)
alert(now.format("dddd, MMMM Do YYYY, h:mm:ss a")); // "Tuesday, March 3rd 2015, 3:25:50 pm"
As I wrote before it supports internationalization, but it requires the locale string:
var now = moment();
now.locale("es"); // set the language to Spanish
alert(now.format("LLLL")); // "martes, 3 de marzo de 2015 8:00"
with the library I created, it's easier to set the right locale:
var now = moment();
var userLcid = Xrm.Page.context.getUserLcid(); //suppose is 1043 (Dutch)
var userLocale = CRMLanguages.getMomentLocale(userLcid);
now.locale(userLocale); // or now.locale(CRMLanguages.getMomentLocale(Xrm.Page.context.getUserLcid()));
alert(now.format("LLL")); // "3 maart 2015 08:00"
Moment.js constructor accepts a standard JavaScript date object, this makes easier to works with CRM form values:
var createdOn = Xrm.Page.getAttribute("createdon").getValue(); // get the date value
var m_date = moment(createdOn); // create the Moment.js object
m_date.locale(CRMLanguages.getMomentLocale(Xrm.Page.context.getUserLcid())); // set the language
m_date.add(3, "days"); // we add 3 days to the createdon date
alert(m_date.format("LLL")); // "2015年3月6日午前8時0分" //1041 Japanese
It's also possible to return the date as a standard JS object, the method is toDate()
// following the previous code
var threeDaysLater = m_date.toDate();
As you can see Moment.js can be very useful, make sure you read the documentation and if you need a multi language support you can use my library. LLAP!

February 9, 2015

JSON and CRM Sandbox Plugins

Today I was working on a plugin and one of the requirements was a call to a web service passing some POST parameters, nothing complicated but I want to share part of the process.

Normally I register my plugins always inside Sandbox, the main reason is that I don't need to care where the plugin is executed (in this specific case the development is OnPremise but the production is Online), the second reason is that my user is often forgotten to be added as Deployment Administrator :)

One of the parameter was a JSON Object passed as string, practically I needed to do the C# equivalent of a JSON.stringify in order to pass a complex structure. An example can be the following Course object (JavaScript):
var Course = new Object();
Course.Name = "CRM Development 1";
Course.Teacher = "Prof. John Smith";

Course.Students = new Array();
Course.Students[0] = new Object();
Course.Students[0].ID = "001";
Course.Students[0].Name = "Walter Davis";

Course.Students[1] = new Object();
Course.Students[1].ID = "002";
Course.Students[1].Name = "Mark Harris";

var parameter1 = JSON.stringify(Course);
Because I was inside a Sandbox plugin I couldn't use the Newtonsoft.Json library and I didn't want to waste time trying to merge it inside my plugin.

The .NET framework provides different methods to create a JSON output, in particular JavaScriptSerializer (from System.Web.Script.Serialization namespace) and DataContractJsonSerializer (from System.Runtime.Serialization.Json namespace).

JavaScriptSerializer is very easy to use (it has a Serialize method returning a string) but doesn't work inside Sandbox, so I used DataContractJsonSerializer:
Course course = new Course();
// ...

string parameter1 = "";
using (MemoryStream memoryStream = new MemoryStream())
   DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Course));
   serializer.WriteObject(memoryStream, course);
   parameter1 = Encoding.Default.GetString(memoryStream.ToArray());
A bit longer code but compatible with Sandbox and CRM Online.

January 27, 2015

New CRM 2015 exams available: MB2-707 Customization & Configuration and MB2-706 Online Deployment

The following CRM 2015 exams are now available:

MB2-706: Microsoft Dynamics CRM Online Deployment
MB2-707: Microsoft Dynamics CRM Customization and Configuration

The exams can be already scheduled and until 31 May 2015 there is also the Second Shot offer, meaning that you can retake the exam for free if you didn't pass the first time.

All the Dynamics CRM 2015 exams are listed here:
Dynamics CRM 2015 Certifications

January 20, 2015

Get the Id of records created using Early Bound OrganizationServiceContext

The other day my friend Raj (@RajYRaman) retweeted a post from Dynamics CRM PFE Team about the best practice to let CRM to choose the Guid of new records instead of using Guid.NewGuid():

With IOrganizationService object is very easy to get the Id of the new record, because it's the value returned by the Create method independently if late bound or early bound is used:
IOrganizationService service = new OrganizationService(crmConnection);

// late bound
Entity lateAccount = new Entity("account");
lateAccount["name"] = "Late Bound Account";
Guid lateAccountId = service.Create(lateAccount);

// early bound
Account earlyAccount = new Account();
earlyAccount.Name = "Early Bound Account";
Guid earlyAccountId = service.Create(earlyAccount);
This works because Early Bound classes inherit from the Entity class.
public partial class Account : Microsoft.Xrm.Sdk.Entity ...
But how we can get the Id of the records when we use the OrganizationServiceContext in combination with the AddObject and SaveChanges methods?
Using Guid.NewGuid() in order to specify the Id before creating the record was one of my mistakes when I first used Early Bound classes. My badly written code was:
IOrganizationService service = new OrganizationService(crmConnection);
XrmContext context = new XrmContext(service);

Account earlyAccount = new Account();
Guid badGeneratedId = Guid.NewGuid();
earlyAccount.Id = badGeneratedId;
earlyAccount.Name = "Early Bound Account";
The reason was to avoid parsing the results of the SaveChanges method in order to get the Id. SaveChanges doesn't return void but a SaveChangesResultCollection object with the details of the save operation.

But checking the results is not necessary, in fact the OrganizationServiceContext updates the tracked records adding the Guid value to the Id property. We just need to read the Id property after the SaveChanges:
Account earlyAccount = new Account();
earlyAccount.Name = "Early Bound Account";

Guid crmGeneratedId = earlyAccount.Id; 
MSDN Documentation (at least at the time I discover this) isn't so clear explaining this behavior, hope it helps!

January 14, 2015

Retrieve the Saved Views (UserQuery) of all CRM users

Dynamics CRM allows the users to create personal views (using Advanced Find) and eventually share them with other users.

The entity used to store these views is called UserQuery and the main properties are:
  • Name: Name given to the saved view
  • FetchXml: String that specifies the query in Fetch XML language
  • OwnerId: Unique identifier of the user or team who owns the saved view
In order to retrieve the saved views we can use FetchXml or a QueryExpression, but the result will always contain only the saved views of the user executing the query.

Consider the following simplified scenario:
  • CRM has only two users: John (System Administrator role) and Bob (Sales Manager role)
  • John has a personal view for the Account Entity (not shared)
  • Bob has a personal view for the Contact Entity (not shared)
If John executes a query to return all the UserQuery records, the result will contain only his Account personal view, despite his System Administrator role.
What if we want to retrieve all the saved views for all the users? A possibility is to impersonate each CRM user, run the query and combine the results.
To implement this solution we rely on the CallerId property of the OrganizationServiceProxy object in combination with the "Act on Behalf of Another User" privilege.
The OrganizationServiceProxy gives us the possibility to impersonate the user by code, the "Act on Behalf of Another User" privilege is required to allow this impersonation.

The following code is a simplified example of the solution:
// url and credentials
string organizationUrl = "";
string userName = "";
string password = "JohnPassword";

// authentication code
ClientCredentials credentials = new ClientCredentials();
credentials.UserName.UserName = userName;
credentials.UserName.Password = password;
IServiceManagement<IOrganizationService> orgServiceManagement = ServiceConfigurationFactory.CreateManagement(new Uri(organizationUrl));
AuthenticationCredentials authCredentials = new AuthenticationCredentials();
authCredentials.ClientCredentials = credentials;
AuthenticationCredentials tokenCredentials = orgServiceManagement.Authenticate(authCredentials);
SecurityTokenResponse organizationTokenResponse = tokenCredentials.SecurityTokenResponse;

// IOrganizationService and OrganizationServiceProxy objects
OrganizationServiceProxy serviceProxy;
IOrganizationService service;

using (serviceProxy = new OrganizationServiceProxy(orgServiceManagement, organizationTokenResponse))
    service = (IOrganizationService)serviceProxy;
    // Dictionary to contain all the saved views
    Dictionary<Guid, Entity> dictPersonalViews = new Dictionary<Guid, Entity>();

    // retrieve first all the CRM Users
    QueryExpression systemUsers = new QueryExpression("systemuser");
    systemUsers.ColumnSet = new ColumnSet(true);
    EntityCollection userCollection = service.RetrieveMultiple(systemUsers);

    // for each User we launch the query to retrieve the saved views
    foreach (Entity systemUser in userCollection.Entities)
        QueryExpression personalViews = new QueryExpression("userquery");
        personalViews.ColumnSet = new ColumnSet(true);

        // we set the CallerId property to impersonate the current iteration user
        serviceProxy.CallerId = systemUser.Id;
        EntityCollection viewCollection = serviceProxy.RetrieveMultiple(personalViews);

        foreach (Entity personalView in viewCollection.Entities)
            // we want a list without duplicates (shared views or automatically shared to SYSTEM and INTEGRATION users)
            if (!dictPersonalViews.ContainsKey(personalView.Id))
                dictPersonalViews.Add(personalView.Id, personalView);

    // we can process the values (Entity objects) of the dictionary
    foreach (Entity personalView in dictPersonalViews.Values)
       string viewName = personalView["name"].ToString();
       string viewFetchXml = personalView["fetchxml"].ToString();
       EntityReference viewOwnerIdRef = (EntityReference)personalView["ownerid"];

January 2, 2015

I am a Microsoft Dynamics CRM MVP!

Yesterday (1 January 2015) I received the Microsoft MVP Award for my contributions to the Dynamics CRM Community. It is a great honor for me to be part of the MVP family.

I work with Dynamics CRM since 2010 but my online presence started 2 years ago. The reason to join several sites at the time was a bit selfish: I was in a new country, the first Dynamics CRM project at my new job was for the most part server side related and I was worried to forget the client side customizations (the big dilemma: getAttribute or getControl? :) ). Helping others to fix their JavaScript looked like a good idea to refresh my memory but shortly I realized how helpful and pleasant is to frequent the various communities and engage with their members.

I would like to thank the following MVPs:
Andrii Butenko: few months after I joined MSDN he sent me this email: “Hello Guido! Found you at MS Forums, checked your blog! Keep up good work!”. Everybody knows Andrii and his contributions so this message means a lot to me.

Jukka Niiranen: I met Jukka in person during a very cold winter, but previously he helped me by email when I was searching a job in Finland. At the time he wasn’t yet an MVP but what he did for me is only a small example of how he likes to help other community members.

Scott Durow, Jason Lattimer and Tanguy Touzard: Because I am a developer their tools and contributions helped me very much during these years. In addition, many of my forum answers are “use the tool ____ from Scott/Jason/Tanguy” and this shows how important they are to the Dynamics CRM Community.

Posted on Friday, January 02, 2015 | Categories: ,