Wednesday, November 19, 2014

Avoiding form reload when switching CRM forms based on a field

Quite often we define different forms for a given entity and we do a form switch based on a field rather than based on the security role of the user. The problem we observe with this approach is that there is often a double form load because when the wrong form loads then record re-loads using the appropriate form. This post offers a solution for avoiding the double form load in this scenario.

Say you have different types of opportunity which are identified by an “Opportunity Type” field and each opportunity type has its own form and BPF. The traditional approach would be to have a JavaScript on load of the form which will navigate to the appropriate form. The issue is that CRM web application loads by default the form that was last used by the given user. So if the user was looking at an opportunity of type “New Sale” and then opens an opportunity of type “New Service Contract” then the service contract opportunity will first load using the “new sale” form and after that form is loaded it will open the correct “service contract” form. The effect is bad user experience because the form loads twice so it doubles the form load time.

I have come across a solution that prevents this double form loading from happening which was suggested by a colleague. The solution is based on the premise that we can change which is the last viewed form for a given user on the fly when the user is retrieving a record. So you can intercept the retrieve operation from a plugin and then look at the type of opportunity that the user is retrieving and then update the user’s last viewed form to be the correct form for the opportunity type that is just getting retrieved. This way, the opportunity is always loaded in the correct form without a form switch.

The solution is a two-step process for the retrieve plugin:

1. In the pre-operation you must make sure to include the “opportunity type” field in the ColumnSet so that it is available later on for you to decide which is the appropriate form for the record.

2. In the post-operation you will have the “opportunity type” value and then you can update the user’s last viewed form to the correct form for the given opportunity type. To do so you simply need to modify the UserEntityUISettings entity for the given user and set the lastviewedformxml field.

Here is some sample code to use from your Retrieve plugin:

on Retrieve plugin
  1. var pluginContext = (IPluginExecutionContext)context;
  2. if (pluginContext.IsPreOperationStage())
  3. {
  4.     var columns = (ColumnSet)pluginContext.InputParameters["ColumnSet"];
  5.     if (!columns.Columns.Contains(OpportunityTypeAttributeName))
  6.         columns.AddColumn(OpportunityTypeAttributeName);
  7. }
  8. else if (pluginContext.IsPostOperationStage())
  9. {
  10.     var currentEntity = context.GetEntityFromContext();
  11.     if (currentEntity == null)
  12.         return;
  13.  
  14.     SetForm(currentEntity.ToEntity<Opportunity>(), service, context.UserId, tracingService);
  15. }

 

Set the correct form
  1. private void SetForm(Opportunity opp, IOrganizationService service, Guid userId)
  2. {
  3.     var query = new QueryExpression(UserEntityUISettings.EntityLogicalName);
  4.     query.Criteria.AddCondition("ownerid", ConditionOperator.Equal, userId);
  5.     query.Criteria.AddCondition("objecttypecode", ConditionOperator.Equal, Opportunity.EntityTypeCode);
  6.     query.ColumnSet = new ColumnSet("lastviewedformxml");
  7.     var settings = service.RetrieveMultiple(query).Entities;
  8.  
  9.     // Some users such as SYSTEM have no UserEntityUISettings, so skip.
  10.     if (settings == null || settings.Count != 1 || opp.pwc_OpportunityType == null) return;
  11.  
  12.     var setting = settings[0].ToEntity<UserEntityUISettings>();
  13.     string formToUse;
  14.     switch ((pwc_contractedengineservice)opp.pwc_OpportunityType.Value)
  15.     {
  16.         case pwc_contractedengineservice.NewSale:
  17.             formToUse = String.Format("<MRUForm><Form Type=\"Main\" Id=\"{0}\" /></MRUForm>", AdHocEngineServiceSaleFormId);
  18.             break;
  19.         case pwc_contractedengineservice.ServiceContract:
  20.             formToUse = String.Format("<MRUForm><Form Type=\"Main\" Id=\"{0}\" /></MRUForm>", ContractedEngineServiceSaleFormId);
  21.             break;
  22.         default:
  23.             return;
  24.     }
  25.     if (!formToUse.Equals(setting.LastViewedFormXml, StringComparison.InvariantCultureIgnoreCase))
  26.     {
  27.         // Only update if the last viewed form is not the one required for the given opportunity type
  28.         var s = new UserEntityUISettings { Id = setting.Id, LastViewedFormXml = formToUse };
  29.         service.Update(s);
  30.     }
  31. }

 

Regarding the supportability of this approach it seems to be a grey area. Technically you are doing operations via the SDK and using regular plugins which seems like a supported approach. The only problem is that the lastviewedformxml field of the UserEntityUISettings which is not documented in the SDK (although the entity seems valid for update).

A few things to consider is that if you are able to make this work with simple security roles then it would be simpler to configure the forms using security roles and you would not need this work-around. Consider as well whether you really need multiple forms or you can have other approaches such as JavaScript to hide/show sections depending on the type of record. I have posted some guidance regarding the different options available: http://gonzaloruizcrm.blogspot.ca/2014/07/different-entity-flavours-new-entity.html

Tuesday, October 14, 2014

Error when importing CRM solution 0x8004F658: The label {}, id: {} already exists. Supply unique labelid values.

I usually don’t post on CRM bugs but this one made me lose so much time that I figured I might share my experience.

We are in the middle of a CRM 2013 upgrade project and while transporting our main solution with all the entities we keep getting this error message: The label '{0}', id: '{1}' already exists. Supply unique labelid values while importing entity forms. I first thought that somehow we messed up the labels since we have a bilingual English and French system. However, after looking at the LocalizedLabel tables I could not find any duplicates of what the error message was talking about.

Then I decided to do a search in all the system forms in the target environment and check where the labelid occurred. I never really found a duplicate but I noiticed that in some cases the label already existed in the target environment in the formXML. This was as expected but to get over the problem I deleted the problematic forms in the target environment (since they will get overwritten by the new solution anyway). Some forms were the system forms so I could not really delete them, therefore I just stripped them down completely to remove all tabs, sections and fields as much as possible to make sure the custom labels would have been deleted.

Now I publish all the customizations and try importing again. Half of the failures are gone but I still get the same error message on some entity forms. I find it weird since the “duplicate” labels are nowhere to be found. I also validated that in my custoizations.xml the labelid was not duplicated, no, each GUID appeared only once.

With no time to call Microsoft support I had to find a work-around. So here’s what I do (not proud of the solution but it got me out of trouble). I open the customizations.xml file and locate the labelid which solution import is complaining about. And I simply generate a new GUID and replace it:

image

In some other cases there was not even a “labelid” attribute on the tab so I just added it with a new Guid. This happened in some tab, section and cell nodes, it was all over the place for some reason.

So after assigning a new Guid to all these sections I was able to import successfully. I never really had the time to go deeper into the root cause, but after talking to a colleague we suspect that the issue stems from the fact that we used the “Merge Forms” button that is available in CRM 2013 to help you upgrade your forms. The issue only occurred for the entities for which we used that feature so therefore our suspicion.

image

I will never be sure but I will try to avoid using that button until I have time to get back to the root cause analysis of this issue, lot of time wasted.

Monday, September 29, 2014

What’s up with CRM not working in latest version of Chrome?

You might have noticed that some features of CRM are no longer working after updating to Chrome 37. This posts provides some insight into work-arounds and possible solutions

All of the sudden seems like the CRM 2011 application is broken for Chrome for some features such as editing workflow send email steps. This is because CRM relies on a web API called showModalDialog() which Google Chrome is no longer supporting as of version 37, you can read more about it here. Note that it was deprecated since version 35 and Firefox has also deprecated that API.

So what can we do about it? Well, the easiest work around for now is to use Internet Explorer while you can find a more permanent solution. Microsoft has also published another temporary work-around so that you can continue to use Chrome: KB3000002. However, there are three bad news with that workaround. The first is that it requires each user to apply the work-around. The second is that users must have a highly privileged account on their work station in order to be able to apply the workaround, and we know that in the enterprise world, very few users will be given enough privileges or access to make the changes suggested by Microsoft. Finally the third bad news is that the work-around is only valid until May 2015!

Microsoft had quite a job to do in order to fix the entire CRM application to remove all the usage of showModalDialog(). This will probably take some time before we are able to apply a patch at the server level which will automatically fix the problem for all users. Perhaps before that happens there will be an easier work-around solution that Microsoft can come up with but don’t get your hopes up.

I guess this article is no good news for those who refuse to give IE a try, it’s one of the consequences of multi-browser support and Microsoft having little control on when other browsers drop support for API's that CRM relies on.

Wednesday, September 17, 2014

Should I use CRM personal views or system views?

When customizing Dynamics CRM the question often arises on whether to use System Views or Personal Views. They both have pros and cons that I will explore in this post.

Let’s first look at what is different. I had previously posted an article about the differences between personal and system views, here is a summary:

 

Personal

System

Ownership

Can be owned by a user or team

Owned by the organization

Visibility

By default it is only visible to the user who creates it and users/teams with whom it was shared

By default it is visible to all users.

Privileges

Can be protected using the standard privilege depths for the entity (none, user, BU, BU and child BU, Organization). This can allow you to make a chart/dashboard accessible to some users but not all and be able to select which users can see which charts/dashboards.

User access can only be configured to all or none (if a user has access to a system chart/view then the user will have access to ALL system charts/views.

Sharing

Can be shared with a specific user or team. For example the CEO might want to share a chart only with a VP.

Cannot share or unshare system views/dashboards/charts since they are all visible at the organization level (by everyone).

Solutions

Cannot be included in a solution. This is a show stopper if you need to move personal views/dashboards/charts across deployments and organizations. You would need to copy them manually. For charts, you can export the XML and import as a system chart.

System views/dashboards are solution aware and are fully supported to be transported in solutions.

CUD operations (Create, update, delete)

Most users will have access to create their own personal views, dashboards and charts.

Only high privileged users and system administrators should have access to CUD operations on system views, charts and dashboards.

 

The problem with system views is that:

  1. Requires IT to create/update and deploy the views
  2. Cannot define which users see the view, all users will see all system views.
  3. Can very quickly clutter the view selector with numerous views making usability a challenge when the users have too many views to choose from.

The problem with personal views is that:

  1. Often leads to excessive sharing, if every user creates views and shares them with the team then volume of views will grow very fast making it hard for users to find the views they actually use.N
  2. Once a view has been shared with you, you cannot “reject” it if you don’t want it. You would have to ask the view owner to un-share it with you.

 

We will explore more in details what best practices can be leveraged to reduce these problems:

  • If the view is only required for a small subset of users it is better to leverage shared personal views
  • If the view is to be changed often by business users then it is easier as personal view.
  • If the view is a default view that everyone needs and does not change often it is better as system view.
  • If your entity already has 10+ system views, you should consider whether you really need to add more system views or if you can manage at the personal view level.
  • If different users need to see different information (e.g. service vs. marketing user) for the same entity then you can leverage personal views shared with a team (service or marketing team)
  • There should be small number of users who are trained to create, maintain and share personal views. You should avoid everyone sharing their own views with everyone else.
  • All users should be trained to create their own personal views but be mindful before sharing it.
  • Before disabling a user in CRM please ask the user to delete, assign or un-share all personal views.
  • When sharing a view, make sure that you also share the “share” privilege so that way you give everyone the chance to opt-out to your view or share with other users:

image

 

What happens to personal views if user leaves the company (disabled user)

If a user has created views and the user has shared these views with multiple users then it can be a problem when the view owner leaves the company and the user is disabled because the shared views continue to be active and all users with whom the views were shared will continue to see those views. However, at this point it is not possible to delete or update the views that were created by a disabled user. If you find yourself in this situation you will have to open the disabled user and click on “Reassign Records” so you can reassign the personal view to a new owner. (Note: This will reassign all the records in the system, not just the system views).

image

 

How to reject personal views

If another user shared with you a view that you don’t want to see, you have 2 options:

  1. If the user who shared the view was kind enough to share with you the “share” privilege then you can easily opt-out to the view by removing yourself or your team from the sharing list.
  2. However, if you are less lucky and the view owner only shared “Read” privileges with you then you will have find out who is the owner of the view and ask them to remove you. To find the view owner go to Advanced Find, select the entity from the dropdown and click “Saved Views” button. Now you can find out who is the owner of the view that you don’t want and you can ask them to remove you.

 

How to assign a personal view

If you created a personal view and you no longer want to maintain it, you can assign it to another user by opening the list of your saved views and clicking “Assign Saved Views”

image

 

 

How can IT identify which personal views were shared and with whom

There is no easy way to find all the shared views in CRM, you can create a custom report in CRM or simply run the following query in your database:

 

select userquery.Name AS 'View Name', userquery.OwnerIdName as 'View Owner', SYSTEMUSER.FullName 'Shared with user', TEAM.Name as 'Shared with Team'

FROM principalobjectaccess

JOIN userquery on objectid = userquery.userqueryid

left outer JOIN SYSTEMUSER on principalid = SYSTEMUSER.SystemUserId

left outer JOIN TEAM on principalid = TEAM.TeamId

WHERE objecttypecode = 4230

 

This will give you a list of all the views, the view owner and the users/teams with whom each view is shared. You can use the SQL statement above to create a CRM report that is available to CRM users from the CRM application.

Monday, September 15, 2014

CRM Auto-numbering: What happens when you reach the maximum number

I’ve often been asked this question about the out-of-the box auto-numbering feature in CRM: What happens to my auto-number when I get over 99’999 cases in CRM?

Let’s look at how auto-numbering works out of the box. Each of the supported entities (contracts, cases, articles, quotes, orders, invoices and campaigns) have the following configuration:

image

Prefix: This is a 1-3 character prefix that you can use to identify which entity the number references. In the example above if you see INV-01000-AS7F you know that this number references an invoice because of the “INV” prefix. This prefix is configurable.

Number: This is a sequential number that will be incremented with each new record. You see this number holds between 4 and 5 digits. Hence the question of what happens if you have more records than the number of digits can support.

Suffix: This is a system-generated random number that is supposed to be unique. I t’s very obscure why there is a need for a suffix, unfortunately it is not configurable and you cannot remove it. All you can do is specify the length between 4 and 6 characters.

 

Initially I thought that when your number goes over 99999 then it will simply change to 100000. However, I could not find any documentation that specifies how this works behind the scenes or what to expect. So I had no choice but to confirm my theory by testing:

image

This is good news. so I even went ahead and tested what happens if you have 1 million cases and found the same result:

image

Therefore there is nothing to worry about (except the lack of documentation).

Monday, July 21, 2014

Mesa de Expertos CRM – Verano 2014

Este 22 de Julio no te pierdas la mesa de expertos sobre la importación de datos a CRM. Estaremos cubriendo 30 tips en 30 minutos, no me había imaginado la cantidad de consejos valiosos que existen sobre la importación de datos.

Como siempre, el evento es organizado por La Comunidad CRM y tiene como panelistas a varios MVPs de habla hispana:

  • Gus Gonzalez (MVP, Zero2Ten)
  • Damián Sinay (MVP, Remoting Coders)
  • Ramón Tebar (MVP, MetroBank)
  • Demian Raschkovan (MVP, Infoaván)
  • Gonzalo Ruiz (MVP, Avanade)
  • Pablo Peralta (MVP, Dynamix UruIT y CRMGamified)
  • Atilio Rosas (MVP, Consultor autónomo)
  • Jimmy Larrauri (Microsoft)

Para todos los detalles y para registrarte, has click aquí.

Monday, July 14, 2014

Different Entity Flavours: New entity, new form or same same?

Often times we have different “flavours” of the same entity. For example, we might have cases related to customer service enquiries and other type of cases related to product failures. So the question often arises: How to best model in CRM these different types of entities, should we use the same entity? the same form? This posts aim to provide some guidance for that scenario.

In the example above, you might be hesitating whether or not these 2 different type of cases should use the same entity and have a simple “qualifying” attribute (dropdown) to identify the case type or whether it makes more sense to have a different custom entity for each case type. If you select the same entity, you might also wonder whether the same form should be used for all case types or(maybe with some dynamic show/hide sections) or whether different forms should be used and route to the correct form depending on the value of the “case type” field.

For the sake of this example, I will stick to the “case type” example, although I have seen the same scenario come up with other entities such as contacts and opportunities. When you find yourself in the situation in which you are not sure whether or not to re-use the same entity or even whether or not to reuse the same forms, here are few questions that can help you get started with your assessment:

1. Do you have different security requirements for each case type? If you have a requirement such that a given role/team can only have access to a specific case type then you' should consider using different entities since it will be much easier to manage the security granularity for each of the case types without having to write and maintain tons of code for it.

2. Do you execute reports and BI on all cases aggregated? In this case if you split your case into multiple entities then your reporting can be more challenging and simple charts such as “case per type” would become a pain to do.

3. How much of the business logic is shared? If most of the business logic applies across the board (e.g. same escalation rules, same custom ribbon commands, etc) then it would be easier to re-use the same entity than having to duplicate all that business logic you implemented using JS or plugins on your entity. Also consider whether you will need some of the out-of-the-box business logic (e.g. escalations or allotments for cases) that you don’t want to re-invent if you use a new entity.

4. How much overlap do you have in the fields of each case type? If the only field that the different case types have in common is the “title” then this is a clear indication that your case types are in essence different entities. It would be annoying for end users when they use advanced find or they are creating views/dashboards that they see a long list of fields but they don’t know which field applies to what case type. If most of your fields are applicable across all case types then it would make more sense to share the same entity.

5. Are the optionset values the same for all your case types? Consider for example the “Source” field. Depending on your case types the applicable values might be different, for example “Twitter” might be a valid source for a customer service case but does not apply to an operational case of equipment failure. Think about the effort required to filter or validate option sets if they are too different for each of your case flavours.

 

By now you might have a better idea of whether or not to re-use the same entity or define a new one; there is no one-size-fits-all or blank/white answer, sometimes you need to consider multiple factors and make a difficult decision based on the information you know (e.g. the questions above). Now, if you decide to re-use the same entity you are left with the question: Should I use the same form or define a new form for each case type? Again, there are pros and cons of each approach and I’ll just attempt to provide you food for thought so you can make a better decision to the question above.

1. You can create a “base” form which has a dropdown for the case type. This would be the form that users would see when creating a new case. Depending on the case that they select then you have JavaScript onload that automatically navigates to the appropriate form. This works very well but there is a bad side effect from user experience: Each time you open a case, the last used form is opened by default even if it is not the correct form for the case type you opened. The user will see a delay in which the old form is loaded and after a few seconds it will forward to the correct form and reload it. If the same user has to deal with multiple forms all the time then this effect can be quite annoying and unfortunately there is no functionality in CRM such that the record opens on a specific form without the “jumping”. However, if typically users will only open a specific case type then it would work fine because the same form will always be used by default and rarely will the user see the form switching automatically.

2. Using additional forms allows you to configure role-base security. However, you should probably not leverage this because if you restrict who can see which forms then users might open a case record in the wrong form and the system is unable to navigate to the appropriate form if the user does not have the required role. If you leverage multiple forms per entity depending on case type then it is recommended you allow all users who have access to case to see all forms for case. You can leverage FLS if you want to hide specific fields.

3. Consider creating a common section/tab on the form which contains all the fields that apply to all cases. Then you can add one tab per each case type and then hide the tab dynamically on-load depending on the value of the case type. This works great from user experience because they don’t see the form “redirecting” and it is much faster than having multiple forms. The down-side is that it could get complex if you have many fields and subsections that overlap with some case types but not others. I usually prefer this approach when things are simple (only a few fields are different).

4. Remember that restricting access on a given form to  given role does not restrict the access on the record itself. If you don’t want your customer service team to see system failure case types then restricting the form will not be enough, they would still be able to open system failure cases but see them from the customer service form (which is odd and can cause confusion). If you really have strong security restrictions consider using separate entities or field level security (FLS).

Sunday, May 11, 2014

Mesa de Expertos “de primavera” en Comunidad CRM

 

To all my Spanish-speaking readers, Comunidad CRM is hosting again an “expert round table” to discuss some of the new features of Dynamics CRM and answer your questions. You can count on the participation of some of the Dynamics CRM MVPs:

  • Gus Gonzalez (MVP, Zero2Ten)
  • Damián Sinay (MVP, Remoting Coders)
  • Ramón Tebar (MVP, MetroBank)
  • Demian Raschkovan (MVP, Infoaván)
  • Gonzalo Ruiz (MVP, Avanade)
  • Pablo Peralta  (MVP, UruIT Dynamix | CRMGamified)
  • Jimmy Larrauri (Microsoft)
  • Atilio Rosas  (MVP)

 

Don’t' miss this event, you can register here and also submit questions through the Comunidad website.

Wednesday, April 30, 2014

Explaining the built-in SYSTEM and INTEGRATION users

If you have played with CRM long enough, you might have noticed the existence of 2 special user accounts: SYSTEM and INTEGRATION. In this post I’ll try to answer the typical questions I get around what these are and what you need to know about them.

Let me start with some of the facts and characteristics about these 2 user accounts which will help us draw some conclusions later on.

 

THE FACTS

1. SYSTEM and INTEGRATION users have a different SystemUserId (Guid) across all CRM organizations (and CRM Online organizations). To get their user ids you’d have to perform a query (or Advanced Find).

2. These users are very well hidden from the application. They are technically “disabled” and they are even filtered out from the “Disabled Users” view. If you’d like to see them you’d have to build your own Advanced Find without the default filters.

3. Nobody can log in CRM as either of these 2 users.

4. These users are read-only. You are not able to change their teams, security, FLS, business unit or any other field.

5. They do not consume a license.

6. These users are always on the root business unit.

7. They don’t have a mailbox or the ability to send or receive emails.

8. No security applies to these users (any action is allowed when executing as SYSTEM or INTEGRATION), all security validations are bypassed.

9. Unofficial fact: Seems like you cannot impersonate these users in CRM Online from outside of plugins. I haven’t been able to impersonate SYSTEM or INTEGRATION from an external application calling into CRM Online (however, works fine on my OnPrem orgs).

 

THE EFFECT

By now you might be wondering why do we even care about these obscure users. The answer is: we shouldn’t; there is a reason why they are so hidden and sometimes unheard of. However, you might also be thinking that if you have another application integrating with CRM then you could make use of [for example] the SYSTEM/INTEGRATION users to make all service calls into CRM. This way, whenever let’s say an audited record is updated via your integration then it will show as updated by INTEGRATION user which could be a neat indicator that the update came from an external system. This is technically possible (quite easy actually), all you have to do is set the CallerId in your proxy (OrganizationServiceProxy.CallerId or CrmConnection.CallerId) to their userId whenever you create your proxy from the external system to call into CRM, this is what it would look like:

image

Similarly, if you are working from a plugin, you can configure the plugin step to execute as SYSTEM or INTEGRATION user. You can configure that in the Plugin Registration Tool or the Development Toolkit:

image

So now that you know how to impersonate these users, let’s explore the details of why we would do so:

 

THE SCENARIOS

1. Elevation of Privileges.

You might have some business logic implemented in plugins which should bypass security checks (e.g. auto-calculated or rollup fields). In that case it might be useful to run the plugin as SYSTEM user. Additionally, you will see in the audit history that the record has been updated by SYSTEM user which gives us a hint that the update was made by an automated logic of a plugin. On the other hand, you have to be very careful when doing this. The reason is that a plugin can trigger another plugin or a workflow and you might end up with a chain of plugins/workflows triggering. Once you elevate the privileges, every action after that will also run in elevated privilege mode (SYSTEM). Therefore you need to make sure that whatever chain of plugins/workflows will be triggered is OK to execute as SYSTEM. For example, sending an email as SYSTEM will fail because that user does not have a mailbox/email address so if anywhere in your plugin chain you send an email then you might have a problem.

Here is an example: You have tasks with different priorities. The priority of the case is automatically taken from the highest priority of the tasks associated. Not all users have access to update the priority of the case but any user can update the priority of their task which will rollup to the case via a plugin. When a case priority is set to “1” a plugin will send a warning email to the case owner. If you impersonate SYSTEM to rollup the task priority to the case priority you need to make sure that in your plugin that sends the email you set the “from” field, otherwise CRM will try to send the email from SYSTEM which won’t work.

 

2. Generic “system” user for system operations

These special users can work whever you want to do actions on the system programmatically and tag the action to a generic system user. A typical example is when you have integrations with other systems and you want the automatic integration in CRM to execute as a generic system user. For example, if you have integration with ERP system, and whenever a new account is created in ERP it should also be created in CRM. In that case the accounts in CRM would be created by “SYSTEM” or “INTEGRATION” user if you use impersonation to perform the account create under one of those user accounts.

 

THE CAVEAT

As I mentioned earlier, impersonating these system accounts can have some bad side effects. The reason is that any action you perform under one of those user accounts can trigger plugins (which can trigger other plugins). You need to be very careful and make sure you understand that your impersonated action can trigger plugins then those plugins will also execute under the context of the SYSTEM /INTEGRATION accounts. Some plugins you might want to always execute under the context of the Calling User (depending on the scenario). The other things to consider is that these special accounts have no User Settings entity associated (they have no language, time zone, format, etc.) and in some cases your customizations might rely on the existence of user settings for every user account. For example, in some plugins you might check that the user’s language or time zone is in order to execute some business logic. This would break if the plugin executes as SYSTEM/INTEGRATION.

Another issue with these accounts is that they are always at the root business unit. If a record is created by one of these accounts, the record will belong to the root business unit which can mean that many users in child business units will have no visibility into the record (unless they have organization-level privileges). You would have to make sure that you assign the records when they get created if you need those records to sit on different business units other than the root.

 

THE CONCLUSION / ALTERNATIVE SOLUTION

Impersonating SYSTEM/INTEGRATION can be useful for some scenarios for privilege elevation purposes; however, it does have its side effects or considerations as explained above. You need to carefully consider whether it makes sense to impersonate these user accounts given the side effects. Another alternative would be to create a new user (e.g. “CRM System User”) in AD and CRM and chose to impersonate that user whenever you are performing “system” transactions that should execute with elevated privileges. The advantage of doing that is that now you can control exactly how you configure that “super user”. You can choose to give it System Administrator role or a more restrictive role. You can decide whether or not to give it field-level permissions and in what business unit the user should be. Furthermore, you can configure a “system” mailbox so that this user is able to send “system” communications to end users (which can be quite useful as well and cannot be done with built-in SYSTEM/INTEGRATION account).

Note that if your plugins are configured to run under the context of a specific user (e.g. CRM System User) then it’s a good practice that this same user exists in all your environments (DEV/QA/Prod) with the same full name. If that is the case then you can safely transport your solutions and the impersonation configuration will be preserved when you transport plugins across your environments because CRM will be able to find the impersonating user by full name (even though the systemuserid and the AD accounts might not match it will still be able to resolve by full name).

Monday, March 17, 2014

CRM 2011 to 2013 (Orion) Top 10 Upgrade Tips and Considerations (part 2)

This is part 2 of my previous post which helps identify tips and gotchas of CRM 2013 upgrade. Click here to read the first part: Top 10 Upgrade Tips and Considerations (part 1).

So it’s time to upgrade to CRM 2013 and the questions we always get are:

What is the impact?

What do I need to do to make sure my organization will continue to work in CRM 2013?

What are the tips and tricks for upgrading to CRM 2013?

How can I estimate the effort to upgrade?

 

So I will attempt to provide a checklist of the top 10 most common aspects of the upgrade that you should keep in consideration. In my previous post I addressed the top 5 and and here are 5 additional considerations/tips:

 

5. Mobility

Because of the limited mobile functionality of CRM 2011 (mobile express) it is easy to assume that there is no effort required to migrate your mobile express application to CRM 2013 except perhaps some testing effort. However, if you are thinking of taking advantage of the new mobile application (e.g. CRM for tablets) in CRM 2013 then you need to consider that if you are in On-Premise you might need to include additional effort to enable IFD if it is not already configured. Mobile application in CRM 2013 is only supported with claims authentication and IFD mode. Additionally, if you have third party mobile solutions (e.g. Resco, CWR) then refer to the previous paragraph.

 

6. AutoSave

If you have used the new forms of CRM 2013 you might have noticed the new Auto-save feature. By default, users are not required to click “Save” when updating a record. Every 30 seconds after editing a field, the form will automatically submit a save request in the background. Clicking some commands such as “new”, “qualify” or closing the form will also issue a save request to the server (note: it does not apply to the create form).

The impact of auto-save is that each auto-save is an update operation in the database so plugins and workflows will trigger and auditing will occur. You need to consider that if the user takes 2 minutes to update a form, then at least 4 updates will actually occur on the server side.

So why is this important for upgrade? Well, you could argue that this is nothing to worry about since this behavior corresponds to how some users are actually saving multiple times in fear of losing their updates, but it is now done automatically. I tend to agree with that but in reality some developers do not consider those type of users when designing plugins or workflows. You need to ensure that your plugins and workflows will not break because of auto-save; one typical recommendation is to limit the “triggering attributes” of an update plugin to only those fields that when changed should really trigger the plugin. This is more of a review exercise and you might not need to make any changes.

On the client side each time auto-save occurs, the fields that changed in the server will be refreshed in the form, but the OnLoad event will not be triggered because the form will not be refreshed. You need to ensure that your form scripts will not break because of this new behavior and if necessary you might need to use the new GetSaveMode() method on your OnSave scripts to identify how the save originated. You can use this same method to prevent auto-save from occurring by intercepting the OnSave and discarding any save originating from auto-save.

Note that you also have the possibility to disable auto-save at the organization level which might make things easier to manage since you don’t need to assess the exact impact of auto-save when upgrading to CRM 2013. However, I think auto-save is a great feature and should not be disabled for the wrong reason (e.g. laziness to manage the upgrade impact).

 

7. New forms, sitemap and navigation

Inevitably your forms and sitemap will need to be reviewed since the navigation and user experience changed so drastically in CRM 2013. It is very likely that you will need to re-design your forms because the new layout is different and you might want to take advantage of the new form features such as the social pane. You will also need to consider the command bar and make sure your 5 visible commands are actually the most common ones, otherwise you might need to adjust the command bar (ribbon). Keep in mind that in CRM 2013 the form tab index has disappeared so if you had large forms in CRM 2011 divided in multiple tabs, users will now need to scroll down to find the tab they are looking for and there is no quick access tab index as we had in CRM 2011. In general, CRM 2013 goes with the principle of simplicity and usability so you might need to simplify your forms, include less fields, maybe split your large forms into multiple forms, etc.

In terms of sitemap, you might have noticed how different it is to navigate different parts of the application. For the most part according to feedback from the community, it has become harder to be efficient when navigating between different modules of the application. Consider the scenario of a service representative talking to a client and then generating a lead from the conversation. The service representative might need to change from “Service” to “Sales” often and this is much harder to do in CRM 2013 as you might need to scroll horizontally to find your entity and the navigation will also disappear by default requiring more clicks to bring it back. Therefore, you might need to optimize your sitemap for your organization to keep users happy with CRM and minimize the amount of time they spend switching areas. For example, making sure that the most common entities appear first in each area so users don’t have to scroll every time to reach the entity tile they need to work with.

 

8. Migrate customizations to built-in features

While you might be sure that your customizations will continue to work after the upgrade, it would be great to consider whether you can “un-customize” the system and migrate some of those customization to use built-in features that are configurable. This will of course increase the amount of effort for the migration but will simplify your deployment and make it more maintainable down the road, it is usually an “easy” sell for customers since the advantage is very evident.

As an example, you might have multiple javascripts that perform conditional actions on forms and maybe most of these can be re-written using Business Rules. If you take the time to replacing your scripts with Business Rules then you gain the advantage that the business rules are configurable, no developer will be required to make changes and additionally since it is now a CRM feature, it could be enhanced in future versions of CRM thus setting you in the right path. Similar examples are: Custom notification in forms can be replaced with the new built-in notification bar, custom Bing maps integration, custom image for records can be replaced with the new image field, etc.

Another example of this is customers that use third party mobile solutions for simple mobile scenarios that can now be supported with CRM 2013 built-in mobile features. If you migrate to the built-in mobile features then you will save considerably on infrastructure, maintenance and licensing.

 

9. Infrastructure

Unless you are working with hosted CRM (e.g. CRM Online), infrastructure is always something you need to consider. I suggest you review the CRM 2013 implementation guide and review all the infrastructure considerations. If you are doing an upgrade, it is always recommended that you setup a parallel environment in CRM 2013 to which you will import the CRM 2011 organization instead of doing an “in-place” upgrade because it will make it easier to do multiple trials and have a better roll-back strategy. It is supported to import CRM 2011 organizations to a CRM 2013 deployment and upgrade the organization during the import. Once the upgrade completes you can decommission the CRM 2011 environment.

Keep in mind that the server and client minimum requirements have changed for CRM 2013 so you need to ensure that you are covered from all angles (operating system, browser support, SQL version, etc.).

Another infrastructure-ish consideration is to have a strategy around the extension tables merge. If you are not familiar with this, here is a brief explanation: In CRM 2011 each entity has a base table and an extension (e.g. custom fields) table. As part of the performance improvements of CRM 2013, these two tables are now merged into a single table. Therefore, when you upgrade an organization from CRM 2011, the table merge is one of the upgrade operations, but it is a special one for a number of reasons:

  • It is not a required upgrade step. You can skip this step and continue to use your upgraded organization. However, there might be some performance implications by not merging the tables
  • Depending on the data volume, it can take a long time to merge these tables which can considerably increase your downtime during upgrade
  • If you decide to skip this step, you can always perform it in the future (another downtime window will be required).

Therefore, when you start testing your upgrade, if you notice that it takes a really long time you might want to defer the table merge to a later date. This way you can split the downtime into 2 different time windows. In order to defer the table merge, there is a registry setting that you need to set during upgrade and then to perform the merge at a later date you need to run the CrmMergeBaseAndExtensionTableTool.exe tool located in the CRM server under c:\Program Files\Microsoft Dynamics CRM\Tools.

 

10. Avoid platform-only upgrade

If you are upgrading to CRM 2013 and are just doing a platform upgrade (upgrade as is, no changes to current customizations), then you should think twice the question: Why do I need this upgrade? CRM 2011 is still supported for some years so the primary motivation for an upgrade is to take advantage of the new features. So if you are just doing a platform upgrade, then the upgrade motivation is questionable.

My recommendation is that when you start the upgrade discussion with your customers, start delivering the message early on that there are multiple advantages of CRM 2013 and that the upgrade is just a great opportunity to explore these. Consider the upgrade planning as the opportunity to adapt your CRM deployment and customizations to whatever CRM 2013 has to offer, simplify the deployment and maintenance of your environment while keeping end users happy and engaged with the application by providing them great new features.

Monday, March 10, 2014

CRM 2011 to 2013 (Orion) Top 10 Upgrade Tips and Considerations (part 1)

This post aims at helping organizations understand the impact of upgrading to CRM 2013 and provides tips for assessing the upgrade considerations. It does not provide an exhaustive list of all the possible items you need to validate before upgrading but it provides a list of the most common ones.

So it’s time to upgrade to CRM 2013 and the questions we always get are:

What is the impact?

What do I need to do to make sure my organization will continue to work in CRM 2013?

What are the tips and tricks for upgrading to CRM 2013? How can I estimate the effort to upgrade?

 

So I will attempt to provide a checklist of the top 10 most common aspects of the upgrade that you should keep in consideration. In this post I will address the top 5 and you can expect a follow-up with the next 5.

 

1. Deprecated Features

Some features from CRM 2011 were already marked as obsolete and have been deprecated in the new version. I am providing a list to the best of my knowledge of those features. If you have a CRM 2011 environment that you upgraded from CRM 4.0 it is more likely that this will be a problem since many CRM 4.0 features/endpoints/APIs have been deprecated although they continued to work on CRM 2011.

A. CRM 4.0 plugins and custom workflow activities. If you have plugin assemblies that were compiled against the CRM 4.0 SDK then you will have to re-write and recompile against the CRM 2013 or CRM 2011 SDK.

B. CRM 4.0 client side scripting. If you have javascripts that make use of the CRM 4.0 client side scripting API (e.g. “crmForm”) you will need to re-write them to make use of the CRM 2013 or CRM 2011 API (e.g. Xrm.Page…).

C. 2007 web service endpoint. If you have any external applications or any code that is referencing the 2007 endpoint (asmx) then you need to update them and recompile then using the CRM 2013 or 2011 SDK and point to the endpoints supported in CRM 2013 such as the OrganizationService (WCF) or the REST endpoint (OData).

D. ISV folder. The ISV folder was made obsolete in CRM 2011 for hosting custom web applications inside the CRM ASP.net root. This folder is no longer supported in CRM 2013 so you would have to move those custom web pages to a separate IIS website.

E. Reduced ribbon support. With CRM 2013 new user experience you might have noticed that the ribbon has been replaced with the new command bar. The good news is that when you upgrade, most of your ribbon customizations will be automatically upgraded to the new command bar model, only a small subset of ribbon controls are no longer supported: Dropdown, MRU Split Button, Textbox, Insert Table, Gallery and Gallery Button, Combobox and Color Picker. It is quite rare to make use of those controls which are no longer supported so most organizations will be fine after the upgrade but it is always a good idea to review your custom ribbon rules and make sure you are not making use of those controls. In case you are, you’d need to replace them by some of the supported command bar controls (see SDK for details). The other important consideration with CRM 2013 is that the command bar will only display 5 commands and the rest need to be expanded by clicking on the “…” icon. Therefore, fore usability, you might need to refactor your command bar to place the true 5 most used commands in the bar and move all others to the “expand” section.

F. Read-optimized forms. This was a short-lived feature of CRM 2011 that came with CRM 2011 UR7 and has been removed in CRM 2013. More information on this feature here.

G. Duplicate detection during create and update in forms. In CRM 2013 duplicate detection will not kick-in during record creation or updating via the application forms. This is a well known limitation and although you might find tons of workarounds on the web (e.g. this article from MVP Adrii Butenko), it is possible that Microsoft will bring this functionality back. For now, you might need to do a custom extension for duplicate detection if you rely on it. Note that duplicate detection is still supported when making SDK calls and setting the SuppressDuplicateDetection optional parameter in your create or update request.

 

2. Unsupported customizations

Having unsupported customizations is never a good idea and I have blogged in the past why: The Risks of Unsupported Customizations in CRM. So it would be a great opportunity to review which unsupported customizations you have since they will likely break when you upgrade to CRM 2013. The tricky question is: How do I identify unsupported customizations? As a rule of thumb, any extensions to CRM that you make which are not explicitly documented in the CRM SDK are unsupported. Now, that might be too vague and sometimes not completely accurate but it is my favorite way of defining unsupported customizations. If you want to be a little more specific here is a list of unsupported customizations that I compiled from the SDK and a couple from experience:

  • JavaScript developers are used to interacting with Document Object Model (DOM) elements in code. You might use the window.getElementById method or the jQuery library. You are free to use these techniques in your HTML web resources, but they are not supported to access elements in Microsoft Dynamics CRM application pages or entity forms. Instead, access to entity form elements are exposed through the Xrm.Page object model. The Microsoft Dynamics CRM development team reserves the right to change how pages are composed, including the ID values for elements, so using the Xrm.Page object model protects your code from changes in how pages are implemented.
  • We do not recommend using jQuery in form scripts and ribbon commands (Note: previously this was called out as unsupported and in CRM 2011 has been updated to “not recommended” in CRM 2013). If jQuery accesses the page DOM then it would be clearly unsupported for CRM forms.
  • Modifications to any .aspx, .css, .htm, .js, .xml, .jpg, or .gif files or the addition of files in the wwwroot directories of the Microsoft Dynamics CRM application, Microsoft Dynamics CRM tools, or Microsoft Dynamics CRM files located at Program Files\Microsoft Dynamics CRM. However, if you have made changes to these files, these files are checked for modifications and will not be overwritten.
  • Modifications to the Microsoft Dynamics CRM website (file and website settings). Custom solutions should be installed in a different website. This includes modifications to the file system access control lists (ACLs) of any files on the Microsoft Dynamics CRM server.
  • Use of client certificates is not supported by the Microsoft Dynamics CRM SDK. If you configure the Microsoft Dynamics CRM website to require IIS client certificates, you will get authentication failures for any applications that were built using the SDK.
  • Modifications to the physical schema of the database, other than adding or updating indexes. This includes any actions performed against the database without using the System Customization capabilities in the web application or using the metadata APIs that are described in this SDK documentation. Modifying tables, stored procedures, or views in the database is not supported. Adding tables, stored procedures, or views to the database is also not supported because of referential integrity or upgrade issues. For Microsoft Dynamics CRM 2013 on-premises deployments, adding indexes is supported per the guidelines in the “Microsoft Dynamics CRM 2013 Implementation Guide.” This applies to all Microsoft Dynamics CRM databases and the Microsoft Dynamics CRM for Microsoft Office Outlook local database.
    When you change the database without using the support methods for system customization, you run the risk of problems occurring during updates and upgrades.
  • Data (record) changes in the Microsoft Dynamics CRM database using SQL commands or any technology other than those described in the Microsoft Dynamics CRM SDK.
  • Referencing any Microsoft Dynamics CRM dynamic-link libraries (DLLs) other than the following:
    • Microsoft.Xrm.Sdk.dll
    • Microsoft.Crm.Sdk.Proxy.dll
    • Microsoft.Xrm.Sdk.Workflow.dll
    • Microsoft.Xrm.Sdk.Deployment.dll
    • Microsoft.Crm.Outlook.Sdk.dll
    • Microsoft.Crm.Tools.EmailProviders.dll
  • The use of application programming interfaces (APIs) other than the documented APIs in the web services DeploymentService, DiscoveryService, Organization Data Service, SOAP endpoint for web resources and OrganizationService.
  • To achieve the appearance and behavior of Microsoft Dynamics CRM, the reuse of any Microsoft Dynamics CRM user interface controls, including the grid controls. These controls may change or be overwritten during an upgrade. We do not recommend that you use or change the Default.css file in the Microsoft Dynamics CRM root installation folder.
  • The reuse of any Microsoft Dynamics CRM JavaScript code, including ribbon commands. This code may change or be overwritten during an upgrade.
  • Modifications to any one of the Microsoft Dynamics CRM forms or adding new forms, such as custom .aspx pages, directly to Microsoft Office Outlook or making changes to .pst files. These changes will not be upgraded.
  • Making customizations except when you use the Microsoft Dynamics CRM supported tools available offline in the CRM for Outlook.
  • The use of custom HttpModules to inject HTML/DHTML into the Microsoft Dynamics CRM Forms.
  • Creating a plug-in assembly for a standard Microsoft Dynamics CRM assembly (Microsoft.Crm.*.dll) or performing an update or delete of a platform created pluginassembly is not supported.
  • Creating an Internet Information Services (IIS) application inside the Microsoft Dynamics CRM website for any VDir and specifically within the ISV folder is not supported. The <crmwebroot>\ISV folder is no longer supported.
  • Editing a solutions file to edit any solution components other than ribbons, forms, SiteMap, or saved queries is not supported. For more information, see When to edit the customizations file. Defining new solution components by editing the solutions file is not supported. Editing web resource files exported with a solution is not supported. Except for the steps documented in Maintain managed solutions, editing the contents of a managed solution is not supported.
  • Silverlight Application Library Caching is not supported.
  • Adding custom indexes is not unsupported but I am not 100% clear about updating/deleting built-in indexes.

 

 

3. Code inspection or analysis tools

There are a number of tools that you can install to help you assess the code that you need to review before upgrading. These tools will typically find most of the issues with your existing code and tell you what action you need to fix them before upgrading. However, do not rely 100% on these tools as they are not fully deterministic, but it is a great deal of help. Here are some examples:

CRM 2013 Custom Code Validation Tool

CRM 2011 Custom Code Validation Tool

Microsoft Baseline Configuration Analyzer for CRM (determines if your CRM 2013 environment is configured according to best practices)

 

4. ISV solutions

If your CRM organization is using third-party solutions it is always required that you reach out to the ISV and validate the compatibility with CRM 2013 before making any assumptions. In some cases the solution will upgrade automatically while in other cases manual intervention will be required to make the ISV solution work with the new version.

 

5. Mobility

Because of the limited mobile functionality of CRM 2011 (mobile express) it is easy to assume that there is no effort required to migrate your mobile express application to CRM 2013 except perhaps some testing effort. However, if you are thinking of taking advantage of the new mobile application (e.g. CRM for tablets) in CRM 2013 then you need to consider that if you are in On-Premise you might need to include additional effort to enable IFD if it is not already configured. Mobile application in CRM 2013 is only supported with claims authentication and IFD mode. Additionally, if you have third party mobile solutions (e.g. Resco, CWR) then refer to the previous paragraph.

Sunday, February 23, 2014

All about outer join queries in CRM 2011 and CRM 2013


Outer joins are the way to implement the commonly requested queries in which you are searching records that have no child records (e.g. All accounts without contacts, all users without security roles, etc.). This article explains what the options are for such queries in CRM.
A recurring business scenario is to have a view of records with no child records (display all leads without activities, accounts that have no contacts, etc.) and we also have sometimes a similar requirement but for N:N relationships, for example, provide a list of all users without security roles. Unfortunately achieving these requirements is not as straight forward as we’d like.
Ideally we’d like to be able to use Advanced Find to produce those results. The reality is that Advanced Find does not support outer join constructs so we are not able to build such queries. So what are the options?

1. CRM 2011 or CRM 2013: SQL Report
If you are not in CRM Online you can always build a custom SSRS report and build your SQL query to provide such results. This is a very straight forward query in SQL. For example, if you’d like to retrieve all leads that have no tasks associated the following query will do:
SELECT lead.FullName
FROM Leads as lead
LEFT OUTER JOIN Tasks as ab
ON (lead.leadId  =  ab.RegardingObjectId)
WHERE ab.RegardingObjectId is null



2. CRM 2011: Use the SDK

You can always use the CRM SDK to query records and process the results. However, there is no way to specify an outer join in a QueryExpression, FetchXML or LinqExpression in CRM 2011 in the way that we need. Note that you might find a “LeftOuter” join type in the JoinOperator enum, however, this will not help you in this case. Therefore if you want to retrieve all leads without tasks you’d have to do multiple queries and filter the results manually in your application. For example, if you are looking for all leads without tasks then you’d have to first retrieve all leads and then retrieve all tasks regarding a lead. Finally, you’d then have to merge the results to exclude from your entire lead list those that have a task from your second query. This is less than ideal because if you have thousands of records then making use of paging can be a challenge. It is also not possible to have a view in CRM that would return these results because the CRM platform in CRM 2011 does not support this construct in a single query operation.



3. CRM 2013: Use FetchXML

The good news is that in CRM 2013 the platform has been improved to be able to process these type of queries. Therefore FetchXML capabilities and syntax have been expanded to be able to use outer joins. For our example of retrieving all leads without tasks, this would be the fetchXML you would need:

<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true">
  <entity name="lead">
    <attribute name="fullname" />
    <link-entity name="task" from="regardingobjectid" to="leadid" alias="ab" link-type="outer">
       <attribute name="regardingobjectid" />
    </link-entity>
    <filter type="and">
        <condition entityname="ab" attribute=" regardingobjectid" operator="null" />
    </filter>
  </entity>
<fetch/>
The bad news is that the Advanced Find interface does not yet support designing these type of queries. However, you can update the fetchXML of your system view or your saved query and it will work (users can access this query via CRM views), nonetheless, they will not be able to design or modify this query in the Advanced Find designer, they can only see the results. To update the FetchXML you would need to export the view in a solution and then modify the FetchXML of your view in the customizations.xml file, then you can import the solution back and voila.


4. CRM 2013: Use QueryExpression

Since we know that besides aggregation capabilities, FetchXML and QueryExpression are equivalent (any FetchXML query can be converted to QueryExpression and vice-versa), then we know that QueryExpression has also been expanded to support outer joins. Here is the CRM 2013 syntax you can use in your QueryExpression for outer joins, the following query returns all leads that have no tasks associated:
QueryExpression qx = new QueryExpression("lead");
qx.ColumnSet.AddColumn("subject");

LinkEntity link = qx.AddLink("task", "leadid", "regardingobjectid", JoinOperator.LeftOuter);
link.Columns.AddColumn("subject");
link.EntityAlias = "tsk";

qx.Criteria = new FilterExpression();
qx.Criteria.AddCondition("tsk", "activityid", ConditionOperator.Null);





Perfect, now what about N:N relationships?

For example, how can you query users that have no security roles associated? (There is an N:N relationship between user and security role). The trick with N:N relationships is that the “intersect” entity is actually an entity that can be queried, therefore you can see N:N relationships as two 1:N relationships:


SystemUser : Role (N:N)

Is the same as the combinaation of these two:


SystemUser : SystemUserRoles (N:1)


SystemUserRoles : Role (1:N)

Every N:N relationship has an intersect entity, in the case of the user : role relationship, the name of the intersect entity is “systemuserroles”. Therefore if you want to query users that have no security roles, this is the same as querying users who have no SystemUserRoles (intersect entity) associated, and that is a simple 1:N relationship. So you could simply do it in this way:

QueryExpression qx = new QueryExpression(SystemUser.EntityLogicalName);
LinkEntity link = qx.AddLink(SystemUserRoles.EntityLogicalName, "systemuserid", "systemuserid", JoinOperator.LeftOuter);
link.Columns.AddColumn("fullname");
link.EntityAlias = "sur";
qx.Criteria = new FilterExpression();
qx.Criteria.AddCondition("sur", "systemuserroleid", ConditionOperator.Null);





Saturday, February 15, 2014

Pregúntale a los expertos CRM!


Para todos los hispanos interesados, nuestros amigos de la Comunidad CRM han organizado un evento este jueves 20 de Febrero para responder preguntas generales de la comunidad. Regístrate y envía tus preguntas para que varios líderes de la comunidad CRM te respondan en vivo.
Las siguientes personas (MVPs o empleados de Microsoft) estarán presentes para responder tus preguntas:
•Gus Gonzalez (MVP, Zero2Ten)
•Damián Sinay (MVP, Remoting Coders)
•Ramón Tebar (MVP, MetroBank)
•Demian Raschkovan (MVP, Infoaván)
•Gonzalo Ruiz (MVP, Avanade)
•Pablo Peralta  (MVP, UruIT Dynamix | CRMGamified)
•Jimmy Larrauri (Microsoft)
No olvides registrarte y enviar tus preguntas!