Saturday, 4 April 2015

Utilizing ‘ReplacePrivileges’ Plugin Message in CRM C#

In this post will talk about ‘ReplacePrivileges’ Message.

Introduction

This message will be triggered if you change the set of privilege collections from a security role.
In short word, when you change the ‘circle’ in the Security Role setting

image

When To Use?

Interfere this Plugin Message can be useful for some situations, example:

1. Prevent human error in security role modification for specific security role that already the best in the production.
Often, we have human error, this one cannot access, that one also cannot access, it can be caused by human error, someone accidently revoke or remove a privilege that is required to perform some action.

In addition, we cannot also remove some basic privilege that must be there to login and access CRM Data, such as System Form, User Settings, etc. This can be prevented by give validation by doing intervention of this plugin message

2. Prevent user from ‘abuse of power’ action
In some organization which is security is very important, you cannot just grant common role person a privilege to delete important records, right? For example for Salesperson, do not delete Competitor record, Customer Service cannot delete existing Case, etc.

But, we cannot just let it go and blame who if that happened? We can prevent it by add some logic in this plugin message.

3. Logging Purpose
We can actually turn on Auditing in CRM, but for advance log, you can put your logging logic into this plugin message.

Sample Code

First Sample, Prevent Any Changes for ‘Salesperson’ Role

Many people are assigned to Salesperson role, so maintaining its security is very important.

You have set correctly a privilege in the development server, you want to make sure that no one, including the new System Administrator (if you resigned) to not be able to make any changes.

So, here is the sample code:

public void Execute(IServiceProvider serviceProvider)
{
     #region must to have

     IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

     IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

     // Create service with context of current user
     IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

     //create tracing service
     ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

     #endregion

     Guid guidRoleId = new Guid();

     if (context.InputParameters.Contains("RoleId"))
     {
         guidRoleId = (Guid)context.InputParameters["RoleId"];               
     }

     //To prevent any changes for Salesperson privilege
     if(GetRoleName(service, guidRoleId).ToString().ToLower() == "Salesperson".ToLower())
     {
         throw new InvalidPluginExecutionException("Please do not modify any changes for Salesperson Role!, do not dare to do it!");
     }
}

private string GetRoleName(IOrganizationService service, Guid guidRoleId)
{
     Entity enRole = null;
     string strRoleName = string.Empty;

     enRole = service.Retrieve("role", guidRoleId, new ColumnSet("name"));

     if (enRole != null)
     {
         strRoleName = enRole.GetAttributeValue<string>("name");
     }
     return strRoleName;          
}

Result:

image

Second Sample, Prevent Grant ‘Delete’ Privilege for ‘Competitor’


We don’t want Salesperson to delete the ‘Competitor’ record and we don’t want any human error to give the privilege (prvDeleteCompetitor).

image

Here is the code:

public void Execute(IServiceProvider serviceProvider)
{
      #region must to have

      IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

     IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

      // Create service with context of current user
     IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

     //create tracing service
     ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

      #endregion

      Guid guidRoleId = new Guid();

       if (context.InputParameters.Contains("RoleId"))
       {
            guidRoleId = (Guid)context.InputParameters["RoleId"];               
       }

      //To prevent changes for Salesperson privilege
       if (GetRoleName(service, guidRoleId).ToString().ToLower() == "Salesperson".ToLower())
       {
            if (context.InputParameters.Contains("Privileges"))
            {
               RolePrivilege[] privileges = (RolePrivilege[])context.InputParameters["Privileges"];
               foreach (RolePrivilege rolePrivilege in privileges)
               {
                    //To prevent granting 'Delete' Access for 'Competitor' Entity
                    if(GetPrivilegeName(service, rolePrivilege.PrivilegeId) == "prvDeleteCompetitor")
                    {
                        throw new InvalidPluginExecutionException("Please do not give this Delete Competitor Privilege for Salesperson");
                     }                           
                }
              }
       }                   
}

private string GetRoleName(IOrganizationService service, Guid guidRoleId)
{
     Entity enRole = null;
     string strRoleName = string.Empty;

     enRole = service.Retrieve("role", guidRoleId, new ColumnSet("name"));

     if (enRole != null)
     {
          strRoleName = enRole.GetAttributeValue<string>("name");
     }

          return strRoleName;          
}

private string GetPrivilegeName(IOrganizationService service, Guid guidPrivilegeId)
{
     Entity enPrivilege= null;
     string strPrivilegeName = string.Empty;

     enPrivilege = service.Retrieve("privilege", guidPrivilegeId, new ColumnSet("name"));

     if (enPrivilege != null)
     {
         strPrivilegeName = enPrivilege.GetAttributeValue<string>("name");
     }

     return strPrivilegeName;
}

Result:

image

image

Checking Privilege Depth

You can use rolePrivilege.Depth also to check, whether this is Basic, Deep, Local or Global Access.

https://msdn.microsoft.com/en-us/library/microsoft.crm.sdk.messages.privilegedepth.aspx

How to Register The Plugin

Message: ReplacePrivileges

Primary Entity: role

Register as Pre-Operation or Post-Operation so far I don’t see any difference for this message, but better you register Pre-Operation stage for validation.

Hope this helps!

Thanks.

Friday, 3 April 2015

Alter Lookup Field/Column Displayed Value In the CRM View Using C#

When the users go to CRM and access the Entity View, the objective is they want to see the list of complete data, that they don’t want to click and enter each record, one by one.

Scenario

Often we can see a field showing same value, but in fact it should refer to different record.
Here is for example:

image

As we can see here, there are many Cases referring to same Customer Name (for example: Adventure Works), but actually, are they? Are they the same ‘Adventure Works’ or not?

Well, if we click one by one, we will know that they are different, because in fact we have  a lot of Adventure Works around the world (can be in Jakarta, Sydney, Singapore, Canada, etc.) or we can just add new columns to display, but it means consume another column space, and imagine every time you need to add the columns to the view, including your personal view.

Now, let’s tweak it little bit, I want to see the Case from which Customer, really, I want to know which the customer, exactly? Is that from Jakarta, Sydney, or any other branch.

Expected Result

I want to get like this:

Case 1        Adventure Works [Jakarta, Indonesia]
Case 2        Adventure Works [Sydney, Australia]

Not only showing ‘Adventure Works’

I want to concatenate the multiple fields into single lookup field column.

The Code

Here is the Sample C# Code to manipulate the lookup displayed value.

public void Execute(IServiceProvider serviceProvider)
 {
     #region must to have

     IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

     IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

     // Create service with context of current user
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

     //create tracing service
     ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

     #endregion

     if (context.OutputParameters.Contains("BusinessEntityCollection"))
     {
          var retrievedResult = (EntityCollection)context.OutputParameters["BusinessEntityCollection"];
          foreach (Entity entity in retrievedResult.Entities)
          {
              //retrieve the CustomerId Entity Reference
              if (entity.Contains("customerid"))
              {
                  EntityReference erCustomerId = (entity.Attributes["customerid"] as EntityReference);

                  if (erCustomerId != null)
                  {
                      //retrieve the customerid detail
                      Entity enCustomer = new Entity();
                      enCustomer = service.Retrieve(erCustomerId.LogicalName, erCustomerId.Id, new ColumnSet("address1_city", "address1_country"));

                      //retrieve the City and Country detail
                      string strCity = enCustomer.Contains("address1_city") ? enCustomer["address1_city"].ToString() : string.Empty;
                      string strCountry = enCustomer.Contains("address1_country") ? enCustomer["address1_country"].ToString() : string.Empty;

                      //alter the displayed value for column name
                      erCustomerId.Name = string.Format("{0} [{1}, {2}]", erCustomerId.Name, strCity, strCountry);
                  }
              }
          }
      }
}

How to Register Your Plugin

Please register your Plugin with the following config:

image

Message: RetrieveMultiple

Primary Entity: incident

Event of Execution: Post-Operation

And it is Synchronous Plugin.

Result


Now, see the result here:

image

*As we can see, you can see the additional information that is in separated fields to be displayed in single column.

This is also can be workaround for the CRM Limitation to only get the column from up to one level related entity.

So, let’s say you have Customer as the Lookup field, then you can only get the Columns from the Account/Contact, you cannot get the Column from the Lookup detail of the Account, for example: Originating Lead's Columns, Account Owner’s columns, etc.

It also does not consume to much space and you can put another detail, concatenate multiple columns into single column display.

Hope this helps!

Thanks.

Enable Modifying Unit Group in Existing Product Using Business Rules

As we know that we cannot modify Unit Group field in the CRM Existing Product record:

image

What if we want to Update the Unit Group?

Well, you can use Data Import feature.

What if we only want to update one record?

We can actually utilize the Business Rules as a trick

1. Create a Business Rule for Product

image

You can do any If, can be Product Name, Product ID or any dedicated field just to trigger the ‘Unlock’

And then for the Action, just make the Unit Group in ‘Unlock’ position.

2. Activate it

3. To Make it works, you need to trigger it (since the onLoad will be overridden by CRM Validation, it will lock again the Unit Group, so you need to change the field value to trigger it)

I just type a (anything actually) in the Description field

image

4. You can change the Unit Group

image

Note

1. This is a trick to update Unit Group just in case you want update in single record, it is useful to avoid hassle in importing effort just for single record
2. Remember to always memorize the field you changed to trigger and change the Default Unit accordingly by the new Unit Group Smile
3. Since CRM blocks it, Do at your own risk. Smile

Thank you!

Skyvia: Backup Your Data via The Sky

In my previous post, I was talking about Skyvia as my introduction part. As we know Skyvia is a product to help the data integration and backup in the cloud, now let’s talk first about the backup.

Since my blog is particularly purposed to Dynamics CRM-all about, so here I want to research its capability in term of CRM Backup and write every single step here, and yes, I try it on CRM Online! (As we know it is not possible for us to do backup CRM Online by ourselves without the favor from Microsoft Team).

Steps

1. Sign up and Login

Now, I have been logged in here:

image

2. Create a New Connection

image

image

Select the Dynamics CRM (for those who are not CRM Users, don’t worry, there are plenty other options)

3. Fill up The Connection Configuration Form and Test Connection, then Save it.

image

4. Go to Backup

image

5. Create New Backup Package, Select Connection

image

6. Select your Object to Backup

Well, to backup, you need to choose what Object you want to Backup, so it is Object-Based Backup.
This is very useful if you want to backup partial data that is very important.

image

7. Now, I want to Backup Account and Contact

image

8.Not only that, when you click the ‘edit’ you can perform more.

image

You can filter to not backup all Fields and can Filter by Condition.

And you can perform grouping: And, Or, and Xor (maybe more powerful than the Advance Find Smile)

image

But, now I don’t want to play around with this, I just want to backup all.

9. Setup Schedule

As usual Database Backup Task, we can set a schedule or just make it as one time only.

image

image

I make it as One-Time only in my research.

image

10. Now Back to the Top and Save it.

image

11. Backup is in Progress…

You can see the status here and you can also Force Backup Now if you already schedule it later but you want to have it immediately

image

Scrolling down, later you can se the Records and also the History of the Backup

image

*History in Calendar View

image

12. You can Create Multiple Backup Packages

image

Just wait, it’s about 30 minutes waiting

Now I can get the Report

image

That’s all the steps to backup your database.

Backup Method

Where is it stored?

According to the Engineer, Jacob Martin
Backed up data is stored in a secure Azure Geo-redundant storage (GRS), and these data are always available for viewing and restoring.

Can we Download the Data?

Skyvia allows downloading backed up data as CSV files. It does not allow downloading them as a database. However, you can view all the backed up data in the browser, filter and search them, and restore them in a couple of clicks.

What’s Next

The question now is what’s next? Is that just a backup? No, actually you can perform Update, Insert, and Delete from the selected Backup

Skyvia Backup Features (Update, Insert, Delete)

As mentioned before, this is not only just backup and that’s all. No. It is like a snapshot, imagine you are running Virtual Machine, then something happened, it gets crashed, you can just recover it all.
Now, imagine in the CRM if you accidently delete or update a record, can you undo it?
You can recover it, but you need the Audit history and recover it programmatically, which is for end users it is not recommended.

Skyvia as The Snapshot-er

Recover the Deleted Records

Back to CRM and I delete one of the Account record

image

As we can see, there is a confirmation box and also warning box as caveats to us to re-think again before we delete any record, because WE CAN”T UNDO THIS ACTION.

And Again…Another box comes up.

image

Now back to the Skyvia backup and then select the backup that we have performed before.

Search the data

image

Now, back to the left side, scroll left horizontally and then tick the selected record

image

It will open the ‘Restore’ account

Now, just click the ‘Insert Records’

Restore is in progress….

image

image

Restored Result

image

You can get the report, 1 record has been Restored, now let’s back to CRM.

And we search the record..

image

The record is successfully restored! Saved the day.

Undo the Changes

We learned how this Skyvia helps us to re-insert the deleted record.

Now, back to CRM and the users suddenly change the City and State to incorrect value, Sydney and DKI Jakarta

image

Now, I want to undo the changes

I go back to Skyvia and then perform the Update

image

Undo Result

image

Then back to CRM

image

The Address is back to its original value!

Delete Record

Now we want to delete the record in CRM easily, you can just delete from its snapshot.

I want to delete Brian Burke

image

We know we can delete it from CRM UI, but let’s try to delete from Skyvia.

image

Now no more Brian Burke in CRM

image

You can later recover it back.

History of Actions

image

image

Overall

Strengths:

- This is very useful for restoring data, undo changes, and delete just from the snapshot as long as you have the backup (of course)
- All the actions are performed in very simple steps with fancy UI
- Definitely No need to have programmatically knowledge
- No effort to create Integration environment, because all-in-one in Cloud
- Every single actions are stored and you can see in the History
As usual, there is no perfect product, so it still has Limitations

Limitations:
- Simple logic only, you can just search the record by keyword, so far no complex logic
- Restoring is object by object, can be positive for particular data (no hassle), but negative for big data
- The restored records concept is using Insert, so it will use the New GUID, so that you will lose the first Guid, lose the relationship as well
- Same concept with previous one, the ‘created on’ field will have today value, not the original ‘created on’ field

Thanks and stay tune in the next post!