The Power of CRM Solutions in Driving Business Success

MS365 Dashboard Sample

In today’s competitive business landscape, CRM (Customer Relationship Management) is so important for driving organizational success. Discover the key reasons why integrating CRM is vital for pushing your business forward. Below is a shot list of benefits that you might get when you adopt CRM and using it on your daily work: 

1️⃣ Operational Efficiency: Streamline your operations and enhance collaboration across teams, which in turn it will boost productivity and efficiency. 

2️⃣ Personalized Engagement: Tailor your interactions to meet individual customer needs, leveraging CRM insights to craft targeted strategies and enhance overall satisfaction. 

3️⃣ Improve Customer Loyalty: Nurture lasting relationships with existing customers by leveraging CRM to identify growth opportunities and promptly address concerns, improving long-term loyalty.  

4️⃣ Data-Driven Decisions: Make informed decisions backed by actionable insights provided by CRM, from sales forecasts to customer behavior trends, driving revenue growth and success. 

🌟 Elevate Your CRM Experience with MS365 Cloud Solutions 🌟 

Take your customer relationships to new heights with MS365 cloud-based CRM.  MS365 CRM offers a suite of tools to streamline operations, personalize interactions, and optimize revenue generation. 

Ready to enhance customer engagement and drive revenue growth? Contact us to learn more about how MS365 CRM can empower your business! 

#CRM #CustomerEngagement #MS365 #CloudSolutions #BusinessGrowth #CustomerSuccess #RevenueOptimization

How to Remove Shared CRM View from database

CRM View

Microsoft CRM is very dynamic platform, it is helping collaborate and interact with team members. One of the cool features on Microsoft CRM, you can built View and share it with other team members.. But what happen, if you as an end user want to see this view anymore and you don’t have control to remove it! on this post, we will show you how to remove the shared CRM view from database.

Because of the nature MS CRM dynamicity, there will be a challenge to figure out and delete the sharing metadata.

The good side of this, all metadata is stored in the MS CRM database, thus you can manage that by updating database. I would like to highlight here, this is not recommended by Microsoft and you can do it in case you ran out of options but there is no guarantee from Microsoft this is will be working for you as well. I personally tried couple of times and worked fine with me.

I’m strongly recommend to take backup of your database before applying this change, so you have a backup point in case something went wrong.

DECLARE @userId UNIQUEIDENTIFIER = 'User ID whom have the view shared'

update PrincipalObjectAccess
set AccessRightsMask = 0  --Remove sharing
where PrincipalObjectAccessId in(

  select  poa.PrincipalObjectAccessId
  from PrincipalObjectAccess      poa
     inner join FilteredSystemUser u 
        on u.systemuserid = poa.PrincipalId
     inner join UserQuery uq 
        on uq.UserQueryId = poa.ObjectId
  where 
        poa.ObjectTypeCode = 4230
     and u.systemuserid = @userId
     and uq.Name in( 
        'Target View Name to remove it'
        )

 )

How to register a Plugin inside MS CRM 4.0 using Registration tool shipped with CRM SDK 4.0 Plugins

Plugins on CRM 4.0 replaces Callouts on CRM 3.0.. Mainly Plugins/Callouts is a custom code that should run to response for an action taken place against specific entity. actions like : Create / Update… and so on. Plugins can response to more events than Callouts.. the more events mean more complexity on registering Plugins if we comparing it with Callouts. Thanks for MS, MS released a registration tool with CRM Sdk 4.0 to make out life easier. I listed below main steps to register Plugins inside CRM 4.0.

To register Plugins on CRM 4.0 Please follow these steps:

  1. Your assembly should be strongly named.

  2. The Class that wills response to actions on activity should be implements IPlugin Interface.

  3. Move your assemblies to C:\Program Files\Microsoft Dynamics CRM\Server\bin\assembly\ folder with any resources needed[like XML configuration file, and so on.].

  4. Open Plugin Registration tool that is shipped with CRM Sdk 4.0
  5. Connect to CRM server, then to CRM instance [NB: Your User Should be in Deployment Manager group.
  6. Register your assembly… to open Register assembly dialog Press (Ctrl +A)
RegisterCRM Plugin Building

7.  Add step to your assembly to open Register new step Press(Ctrl + T).. Step is representing a response to an Action happened on CRM Entity..
For example: to response to an Update Event for Contact Entity.. You have to add a step like the following Picture:

Register%20CRM%20Plugin2

How to audit Privileges changes (Security Role) on MS CRM 4.0

One of the challenges that I face while I was working on auditing changes on the major entities at MS CRM 4.0 is to audit changes on Privileges… CRM 4.0 consider privilege entity as a special case this is why we need to build a special case for auditing changes here.

First of all, there is no supported Message (Event) to handle this case. So we have to enable couple of messages to be able to audit changes on security Roles. Those messages called:

1. AddPrivileges. –> this event will handle the creating of a new Security Role

2.  ReplacePrivileges. –> this message will handle adding/update/delete security privileges

The below script help you to enable those messages:

 

/*

Enable Privileges unsupported messages

*/

DECLARE @Message1   uniqueidentifier

DECLARE @Message2 uniqueidentifier

SET @Message1= (SELECT SdkMessageId FROM SdkMessageBase WHERE [Name] = ‘ReplacePrivileges’)

SET @Message2 = (SELECT SdkMessageId FROM SdkMessageBase WHERE [Name] = ‘AddPrivileges’)

UPDATE SdkMessageFilterBase SET IsCustomProcessingStepAllowed = 1

WHERE SdkMessageId = @Message1

UPDATE SdkMessageFilterBase SET IsCustomProcessingStepAllowed = 1

WHERE SdkMessageId = @Message2

After applying the above data script to MSCRM DB, you will be able to find those messages exposed for you on MS CRM 4.0 Plugin Registration tool so you will be able to register a plugin against those messages (Events). Now we need to build the Message handler to audit any changes, below steps showing you how to do that.

I am going to use the same code that audit regular entities with a little adjustments, the reasons of my adjustments are:

· The Pre-Image and Post-Image will not have enough info that needed by Audit handlers; thus I need to add a special handler for that.

· The Audit code that you can download from internet does not handle ReplacePrivileges/AddPrivileges Messages, thus we need to build a special case for that.

Note: To read more about default Audit component please refer to this article: "Auditing MS CRM 4.0 Record Changes"

To solve the problem (A), I have to add a Pre Handler for ReplacePrivilege Message, this is a very simple handler that will contains code to store the current Privileges (before doing update) on a shared Variable to be passed to the Post ReplacePrivilege message handler; below is the code of PreReplacePrivilege Message handler:

public class PreReplacePrivilege:IPlugin

{

#region IPlugin Members

public void Execute(IPluginExecutionContext context)

{

if (context.MessageName == "ReplacePrivileges")

{

if (context.InputParameters.Contains("RoleId"))

{

context.SharedVariables["PreRolePrivilagesValues"] =

Utilities.GetRolePrivilages(context.InputParameters.Properties["RoleId"].ToString(),

context.CreateCrmService(true));

context.SharedVariables["RoleId"] = context.InputParameters.Properties["RoleId"];

}

}

}

#endregion

}

static class Utilities

{

/// <summary>

/// Get privilages for Roles.

/// </summary>

public static Dictionary<string, string> GetRolePrivilages(string roleId, ICrmService crmService)

{

Microsoft.Crm.Sdk.Query.AllColumns cols = new Microsoft.Crm.Sdk.Query.AllColumns();

Dictionary<Guid, string> privileges = GetAllPrivilages(crmService); //Get list of all privileges

RetrieveRolePrivilegesRoleRequest request = new RetrieveRolePrivilegesRoleRequest();

request.RoleId = new Guid(roleId);

RetrieveRolePrivilegesRoleResponse response = (RetrieveRolePrivilegesRoleResponse)crmService.Execute(request);

Dictionary<string, string> rolePrigivleges = ResolvePrivilageNames(privileges, response.RolePrivileges);

return rolePrigivleges;

}

/// <summary>

/// Get all privilages on system

/// </summary>

/// <param name="crmService"></param>

/// <returns></returns>

private static Dictionary<Guid, string> GetAllPrivilages(ICrmService crmService)

{

Dictionary<Guid, string> privilages = new Dictionary<Guid, string>();

RetrievePrivilegeSetRequest requestp = new RetrievePrivilegeSetRequest();

requestp.ReturnDynamicEntities = true;

RetrievePrivilegeSetResponse responsep = (RetrievePrivilegeSetResponse)crmService.Execute(requestp);

foreach (DynamicEntity p in responsep.BusinessEntityCollection.BusinessEntities)

{

privilages.Add( ((Key)p["privilegeid"]).Value,p["name"].ToString());

}

return privilages;

}

/// <summary>

/// Resolving Privileges Names

/// </summary>

/// <param name="rolePrivilegesList"></param>

/// <param name="crmService"></param>

/// <returns></returns>

public static Dictionary<string, string> ResolvePrivilageNames(RolePrivilege[] rolePrivilegesList, ICrmServicecrmService)

{

Dictionary<Guid, string> privileges = GetAllPrivilages(crmService);

Dictionary<string, string> rolePrigivleges = new Dictionary<string, string>();

foreach (RolePrivilege r in rolePrivilegesList)

{

rolePrigivleges.Add(privileges[r.PrivilegeId], r.Depth.ToString());

}

foreach (KeyValuePair<Guid, string> pair in privileges)

{

if (!rolePrigivleges.ContainsKey(pair.Value))

rolePrigivleges.Add(pair.Value, "None");

}

return rolePrigivleges;

}

public static string GetPropertyValue(object property)

{

try

{

if (property.GetType() == typeof(CrmBoolean))

return ((CrmBoolean)property).Value.ToString();

else if (property.GetType() == typeof(CrmDateTime))

return ((CrmDateTime)property).UserTime.ToString();

else if (property.GetType() == typeof(Owner))

return ((Owner)property).Value.ToString();

else if (property.GetType() == typeof(Lookup))

return ((Lookup)property).Value.ToString();

else if (property.GetType() == typeof(Picklist))

return ((Picklist)property).Value.ToString();

else if (property.GetType() == typeof(StringProperty))

return ((StringProperty)property).Value.ToString();

else if (property.GetType() == typeof(LookupProperty))

return ((LookupProperty)property).Value.name.ToString();

else if (property.GetType() == typeof(OwnerProperty))

return ((OwnerProperty)property).Value.name.ToString();

else if (property.GetType() == typeof(PicklistProperty))

return ((PicklistProperty)property).Value.name.ToString();

else if (property.GetType() == typeof(CrmDateTimeProperty))

return ((CrmDateTimeProperty)property).Value.UserTime.ToString();

else if (property.GetType() == typeof(KeyProperty))

return ((KeyProperty)property).Value.Value.ToString();

else if (property.GetType() == typeof(CrmBooleanProperty))

return ((CrmBooleanProperty)property).Value.Value.ToString();

else if (property.GetType() == typeof(CrmDecimalProperty))

return ((CrmDecimalProperty)property).Value.Value.ToString();

else if (property.GetType() == typeof(CrmFloatProperty))

return ((CrmFloatProperty)property).Value.Value.ToString();

else if (property.GetType() == typeof(CrmMoneyProperty))

return ((CrmMoneyProperty)property).Value.Value.ToString();

else if (property.GetType() == typeof(CrmNumberProperty))

return ((CrmNumberProperty)property).Value.Value.ToString();

else if (property.GetType() == typeof(CustomerProperty))

return ((CustomerProperty)property).Value.name.ToString();

else if (property.GetType() == typeof(CustomerProperty))

return ((CustomerProperty)property).Value.name.ToString();

else if (property.GetType() == typeof(StatusProperty))

return ((StatusProperty)property).Value.name.ToString();

else if (property.GetType() == typeof(StateProperty))

return ((StateProperty)property).Value.ToString();

else if (property.GetType() == typeof(UniqueIdentifierProperty))

return ((UniqueIdentifierProperty)property).Value.Value.ToString();

else if (property == null)

return "";

else

return property.ToString();

}

catch

{ return ""; }

}

public static AuditDifferenceCollection AddDifferences(IMetadataService metaService, string entityName,DynamicEntity preImage, DynamicEntity postImage)

{

AuditDifferenceCollection col = new AuditDifferenceCollection();

System.Collections.Hashtable attributesMetadata =

MetadataHelper.GetEntityAttributeMetadata(metaService, entityName, Microsoft.Crm.Sdk.Metadata.EntityItems.IncludeAttributes);

//For any Message that has a Pre and/or Post Image. I will add the values to the Audit Differences.

foreach (Property prop in preImage.Properties)

{

if(prop.GetType()!= typeof(KeyProperty) && prop.Name!="modifiedby" && prop.Name != "modifiedon" && prop.Name != "createdby" && prop.Name != "createdon")

{

col.Add(new AuditDifference(prop.Name,attributesMetadata[prop.Name].ToString(),Utilities.GetPropertyValue(prop), ""));

}

}

foreach (Property prop in postImage.Properties)

{

if(prop.GetType()!= typeof(KeyProperty) && prop.Name!="modifiedby" && prop.Name != "modifiedon" && prop.Name != "createdby" && prop.Name != "createdon")

{

if (col.Contains(prop.Name))

{

AuditDifference diff = col[prop.Name];

diff.CurrentValue = Utilities.GetPropertyValue(prop);

col[prop.Name] = diff;

}

else

{

col.Add(new AuditDifference(prop.Name, attributesMetadata[prop.Name].ToString(),"",Utilities.GetPropertyValue(prop)));

}

}

}

return col;

}

public static AuditDifferenceCollection AddDifferences(IMetadataService metaService, string entityName,Dictionary<string, string> preValues, Dictionary<string, string> postValues)

{

AuditDifferenceCollection col = new AuditDifferenceCollection();

if (preValues != null && preValues.Count > 0)

{

foreach (KeyValuePair<string, string> entry in preValues)

{

col.Add(new AuditDifference(entry.Key + ":" + entry.Value, entry.Value, ""));

}

}

if (postValues != null & postValues.Count > 0)

{

foreach (KeyValuePair<string, string> entry in postValues)

{

if (col.Contains(entry.Key + ":" + entry.Value))

{

AuditDifference diff = col[entry.Key + ":" + entry.Value];

diff.CurrentValue = entry.Value;

col[entry.Key + ":" + entry.Value] = diff;

}

else

{

col.Add(new AuditDifference(entry.Key + ":" + entry.Value, "", entry.Value));

}

}

}

return col;

}

public static string AddAttributeToList(string value, string list)

{

if (list.IndexOf(value) == -1)

{

if (list == "")

list = value;

else

 

===================================

To solve problem (B) we need to adjust the Execute Method of  Create Class… this is the full code after adjustment for Create class:

==================================

public class Create : IPlugin

{

string m_config;

string m_secureConfig;

private string _relationshipAttribute = "";

private string _primaryAttribute = "";

public string Config

{

get { return m_config; }

set { m_config = value; }

}

public string SecureConfig

{

get { return m_secureConfig; }

set { m_secureConfig = value; }

}

public Create(string config, string secureConfig)

{

m_config = config;

m_secureConfig = secureConfig;

if (config.Trim() != "")

{

string[] configSettings = config.Split(‘;’);

for (int i = 0; i < configSettings.Length; i++)

{

string[] values = configSettings[i].Split(‘=’);

switch (values[0].ToString().ToLower())

{

case "primaryattribute":

_primaryAttribute = values[1].ToString().Trim().ToLower();

break;

case "relationshipattribute":

_relationshipAttribute = values[1].ToString().Trim().ToLower();

break;

}

}

}

}

public void Execute(IPluginExecutionContext context)

{

string attributes = "";

Audit audit = new Audit();

DynamicEntity PreImage = new DynamicEntity();

DynamicEntity PostImage = new DynamicEntity();

audit.Service = context.CreateCrmService(true);

audit.MetadataService = context.CreateMetadataService(true);

audit.EntityName = context.PrimaryEntityName;

audit.Type = context.MessageName;

audit.Name = context.PrimaryEntityName + " " + context.MessageName;

Dictionary<String, String> preValues = new Dictionary<string, string>();

Dictionary<String, String> postValues = new Dictionary<string, string>();

if (context.InputParameters.Properties.Contains("Target") &&

context.InputParameters.Properties["Target"] is Moniker)

{

Moniker moniker = (Moniker)context.InputParameters.Properties["Target"];

audit.RecordID = moniker.Id.ToString();

}

else if (context.InputParameters.Properties.Contains("EntityMoniker") &&

context.InputParameters.Properties["EntityMoniker"] is Moniker)

{

Moniker moniker = (Moniker)context.InputParameters.Properties["EntityMoniker"];

audit.RecordID = moniker.Id.ToString();

}

else if (context.InputParameters.Properties.Contains("Target") &&

context.InputParameters.Properties["Target"] is DynamicEntity)

{

//if the transaction is create then get the ID from the OutputParameters

if (context.OutputParameters.Contains("id"))

audit.RecordID = context.OutputParameters.Properties["id"].ToString();

//pull the KeyProperty from the entity.

foreach (Property prop in ((DynamicEntity)context.InputParameters.Properties["Target"]).Properties)

{

if (prop.GetType() == typeof(KeyProperty))

{

audit.RecordID = ((KeyProperty)prop).Value.Value.ToString();

break;

}

}

}

else if (context.MessageName == "ReplacePrivileges" ||

context.MessageName == "AddPrivileges" ||

context.MessageName == "RemovePrivilege") //Unsupported Message and it needs special handling

{

if(context.InputParameters.Properties.Contains("RoleId"))

audit.RecordID = ((Guid)context.InputParameters.Properties["RoleId"]).ToString();

else if(context.SharedVariables.Contains("RoleId"))

audit.RecordID = ((Guid) context.SharedVariables["RoleId"]).ToString();

//Pull Pre state of previleges from shared variable populated on PreStage plugin

if (context.SharedVariables.Contains("PreRolePrivilagesValues"))

preValues = (Dictionary<string, string>)context.SharedVariables["PreRolePrivilagesValues"];

else

preValues = new Dictionary<string, string>();

if (context.InputParameters.Contains("Privileges"))

{

postValues = Utilities.ResolvePrivilageNames(

(RolePrivilege[])context.InputParameters["Privileges"], audit.Service);

}

else

postValues = new Dictionary<string, string>();

}

//Get the Pre and Post Images

if (context.PreEntityImages.Properties.Contains("Images") && context.PreEntityImages.Properties["Images"] isDynamicEntity)

PreImage = (DynamicEntity)context.PreEntityImages.Properties["Images"];

if (context.PostEntityImages.Properties.Contains("Images") && context.PostEntityImages.Properties["Images"] isDynamicEntity)

PostImage = (DynamicEntity)context.PostEntityImages.Properties["Images"];

//Compare the images values.

//Messages not support Pre/Post images.

if (string.IsNullOrEmpty(PreImage.Name) && string.IsNullOrEmpty(PostImage.Name))

{

audit.AuditDifferences = Utilities.AddDifferences(audit.MetadataService, context.PrimaryEntityName, preValues, postValues);

}

else //Messages support Pre/Post images.

{

audit.AuditDifferences = Utilities.AddDifferences(audit.MetadataService, context.PrimaryEntityName, PreImage, PostImage);

}

foreach (AuditDifference diff in audit.AuditDifferences)

{

if (context.MessageName == "AddPrivileges" || context.MessageName == "ReplacePrivileges")

diff.ReplaceAttributeName = string.Format("Privilege:{0}", diff.AttributeName.Substring(0, diff.AttributeName.IndexOf(":")));

if (diff.CurrentValue != diff.PreviousValue)

attributes = Utilities.AddAttributeToList(diff.AttributeDisplayName, attributes);

}

if (_relationshipAttribute.Trim() != "" && audit.RecordID.Trim() != "" && context.MessageName != "Delete")

audit.Relationship = new EntityRelationship(_relationshipAttribute, new Guid(audit.RecordID));

audit.Attributes = attributes;

if (!context.PreEntityImages.Properties.Contains("Images")

&& !context.PostEntityImages.Properties.Contains("Images")

&& context.MessageName != "ReplacePrivileges")

{

audit.Create();

}

else

{

if (attributes.Trim() != "")

audit.Create();

}

}

I am attaching below the  3 classes I made changes on, I got the original classes and do the changes to them.

I hope you will have this useful and will solve your issue

 

Download Code

Maximize MS CRM 4.0 pop-up Windows

Recently a customer asked me how to open the PhoneCall window(any window) form in a larger window.  It’s a simple matter of a little bit of JavaScript.

  • Navigate to the main Account form as follows:
    1. Go to Settings
    2. Choose Customization
    3. Choose Customize Entities
    4. Open PhoneCall Entity ( or any entity you want)
    5. Go to Forms and Views
    6. Open Form window
    7. Open the Form Properties window
    8. On the Events tab make sure that OnLoad is highlighted, then click Edit.  Paste the following Javascript code and make sure that the box for Event is enabled is checked.

//resize window to full screen upon opening Account form
window.moveTo(0,0);
window.resizeTo(screen.availWidth, screen.availHeight);

  • (Below is the screenshot of what this should look like.)
  • Save and close the form, then Publish the entity.

Script Window

How to Hide/Show fields on Advanced Find’s Search Criteria

One of my client requested to hide/Show some fields on MS CRM 4.0 (Microsoft CRM 4.0) Advanced Find search criteria. When this requirement came to me, I thought it is very hard to be implemented. In fact, this is a configuration issue and it is very easy to be implemented. all what you have to do is the followings:

  1. Go to customization area

  2. Open the entity that have those fields

  3. Go to Attributes on the left hand side

  4. Open the attribute (Field) you want to Hide/Show on the search criteria when you use Advance Find
    There is a field Called Searchable ; if you make it True, then you will be able to see this field on advance Find Search criteria, if you make it False, then you will not be able to see it.

  5. Save your changes.

  6. Publish customization

Audit MS CRM 4.0 Record Changes

It is common requirements to audit changes on certain entities on MS CRM 4.0. Unfortunately there is not built-in functionality to do so shipped out of the box. The best solution to solve this issue is to build a plug-in to take care of auditing changes. One of the challenges I faced is: How to implement this plug-in in generic away? So when my client ask me to audit a new entity, I will be ready for that with no Development effort.
The best solution I found to solve the issue is to build a generic entity to hold on your audited information. This entity should be generic enough to hold any kind of info you need…
I found a good plugin on internet and I used it.. It is pretty good to audit the chanag4es on CRM data. Please refer to the below link:
http://code.msdn.microsoft.com/auditing4crm