ian.blair@softstuff

My technical musings

Writing data to a Won Opportunity

Having recently had a requirement to tag Won opportunities with new data to show that they have been superseded it initially seemed that the only way to do it was to reset the opportunity to draft, update it, then reset it back to Won, which is not an ideal method especially as it then marks the opportunity as Won on the day that you reset it.

It turns out that the method to do this is extremely simple, and is perhaps a little hacky and might not last forever but for now it works.

The simplest way to do it is to

Entity newEntity = new Entity("opportunity");
newEntity.Id = opportunity.Id;
newEntity.Attributes.Add("myfield_toupdate", true);
OrganizationService.Update(newEntity);

That's all there is to it, while you can't update the entity you return in a search, you can create a new one, assign the ID and then update the field and Update that.  Simple.

 

Of course it might be worth while adding a fallback routine in just in case.

try {
     ServiceContext.Trace("First try to just edit the fields");
     Entity newEntity = new Entity("opportunity");
     newEntity.Id = opportunity.Id;
     newEntity.Attributes.Add("myfield_toupdate", true);                                       
     OrganizationService.Update(newEntity);           
    }
catch {                                   
         var statusCode = opportunity.StatusCode;
         var dateClose =  opportunity.ActualCloseDate;
         SetStateRequest setState = new SetStateRequest {
                      EntityMoniker=opportunity.ToEntityReference(),
                      State=OpportunityState.Open.ToOsv()  ,
                      Status = opportunity_statuscode.InProgress.ToOsv()
                      };
         OrganizationService.Execute(setState);
         opportunity["myfield_toupdate"]=true;                                        
         var win = new WinOpportunityRequest {
                      OpportunityClose = new OpportunityClose {
                      OpportunityId = opportunity.ToEntityReference(),
                      ActualEnd = dateClose,  // slight bug in api causes this to ignored
                      ScheduledEnd = dateClose 
                      },
                      Status = statusCode,                                           
                      };
         OrganizationService.Execute(win);                                    
       }

By adding the old school make draft, update, then win code in the try..catch block at least if in future versions this code stops working then at least your code wont get any unexpected surprises, at least apart from the ActualEnd date as that always seems to be set to the date you fire the WinOpportunityRequest even if you set it explicitly.

 

 

Who owns your workflows?

Most CRM systems I have seen have a mixture of workflow and dialog owners, usually the person who created them.  But this could lead to a problem, consider the scenario when a key employee leaves and your IT team are a little over zealous in removing their computer access. Suddenly all or at least some of your business processes stop working, and you might not even notice for a while. Disaster!


It makes sense to assign all your workflows and dialogs to a single, ideally non-employee user, or at least an employee who will never leave to stop this happening.

A disadvantage of re-assigning a workflow to a new user is that the workflow will need to be deactivated as part of the assignment, then manually re-activated. For a live system this might mean small disruptions in service, or a long boring out of hours session if you are tidying up and have a lot of workflows to assign.

A much simpler method is to simply log in as the new owner of the workflow and assign them to yourself. This has the advantage that they can be done in bulk and the workflows won't be deactivated.

So to do this log in as the new user. (The new user must have rights to run these tasks)

Then open the list of Activated Processes.

To assign an individual workflow, or multiple workflows by selecting them in the list then press the button.

You will see this dialog.

Then press Assign.

After a few moments the workflows will be assigned to the new user. 

This is by far the least troublesome method.

 

Moving forwards

When a user creates a workflow it is good practice to set the owner of the workflow when it is first created, and the easiest way is to go to the Administration tab of the workflow designer and update the Owner in there.

 

 

Convert a managed solution to an unmanaged solution

Consider this scenario although I will stress that it is strictly hypothetical as it would be completely stupid to never take any backups, and everybody has a good backup strategy in place especially for portable devices. While you are configuring the CRM system you have a consultant who has a CRM virtual machine on his laptop which is the development environment where all the initial changes happen. Once everyone is happy with the changes it then gets moved across to your production system as a managed solution set. For a while this works very well. However at some point his laptop dies or gets stolen, doesn't really matter either way but you quickly realise there are no backups of the laptop, and more importantly the solution on your production system is managed so it won't be easy to setup a new development system.

Fortunately there is a way around this problem, although you won't be able to convert the live system to unmanaged but you will be able to create a new organisation and import the unmanaged version of the solution into it, effectively recreating the original development environment hopefully with a better backup strategy.

To do this you will require the managed solution file that was used to import the solution into your live environment, it will have a .zip extension and be called something like MySolution_Managed_1_0_0_0.zip, and hopefully that will be checked into your version control system, or at least left in a folder somewhere on a server or desktop.

Once you have the file unzip it into a temporary location.

Open the solution.xml file in either an xml editor or notepad.

Within the file find the node

<Managed>1</Managed>

and change it to

<Managed>0</Managed>

Save the file, and re-zip the solution.

Now create your new environment/organisation and import the solution file normally.

Of course when you transfer the solution in future it makes life easier if you export two versions from your development environment, one managed, and one unmanaged and keep copies!.

 

Of course it goes without saying that if you don't own the copyright to any solution files then don't convert them, this applies to various ISV solutions, add-ons etc.

Create an identical Queue in another CRM system

Sometimes when you have multiple environments creating workflows or dialogs in your DEV system can prove to be an issue especially if queues are involved. Queues aren't included in solutions and if you create a queue with the same name in another system it will have a different Guid so when the workflow that references that is moved across to production it will break.

Its pretty straightforward to create a queue with the same Guid in another system and only requires a few lines of code.

First get the Guid of your queue.

Find the Guid in the settings -> business management -> queues screen and then press the pop-out button. (Highlighted here with an arrow).

Once you have this in the location bar of the new browser window you will see the string that contains the Guid for the queue.

"https://crm2016/testorg/main.aspx?etc=2020&extraqs=&histKey=885738149&id=%7b3D9CB3AB-C26B-E711-80FE-005056877901%7d&newWindow=true&pagetype=entityrecord#78522619

You will need the highlighted section, although the guid will of course be different on your system.

The using Visual Studio and the latest version of the Dynamics365 API you will need code like this.

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Tooling.Connector;
using System.ServiceModel;

var connectionString = "url=https://mycrmsystem; Username=myusername; Password=mypassword; authtype=Office365";
CrmServiceClient conn = new CrmServiceClient(connectionString);
using (OrganizationServiceProxy orgService = conn.OrganizationServiceProxy) {
     if (conn.IsReady) {
       
               Entity newq1 = new Entity("queue");
               newq1["queueid"] = new Guid(<the guid from above>);
               newq1["name"] = "<The name of the queue>";
               orgService.Create(newq1);‚Äč
      }
}

 

This can be wrapped in your favourite type of program, console app or windows form based program and when run will create a queue that you can reference in workflows and dialogs and won't get broken when they move across into production.

 

 

 

Old vs New Connection Methods in CRM2016/Dynamics 365

With the release of the new version of the Crm2016/Dynamics365 SDK the recommended method to connect to CRM in code has changed.

Originally it was (although other methods were available with connection strings)

using Microsoft.Xrm.Sdk;
using System.ServiceModel.Description;

var url = "http://mycrmsystem/XRMServices/2011/Organization.svc";
var username = "myusername";
var password = “mypassword";
var domain = ""
var organizationUri = new Uri(url);            
var credentials = new ClientCredentials();
credentials.UserName.UserName = domain + username;
credentials.UserName.Password = password;
credentials.Windows.ClientCredential.UserName = username;
credentials.Windows.ClientCredential.Password = password;
credentials.Windows.ClientCredential.Domain = domain;

using (OrganizationServiceProxy _service = new OrganizationServiceProxy(organizationUri, null, credentials, null)) {
// code to do stuff goes here
}

With the advent of the tooling connector dll and the other changes in the api this should now be changed to:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Tooling.Connector;
using System.ServiceModel;

var connectionString = "url=https://mycrmsystem; Username=myusername; Password=mypassword; authtype=Office365";
CrmServiceClient conn = new CrmServiceClient(connectionString);
using (OrganizationServiceProxy orgService = conn.OrganizationServiceProxy) {
     if (conn.IsReady) {
          // code to do stuff goes here
      }else{
         throw new invalidoperationexception(conn.LastCRMError);
      }
}

The key now is the connection string as this determines the connection method with the authtype= parameters.

The example above assumes you are connecting to a Office365 hosted CRM system but if you were connecting to an on-premise active directory system the connection string might be

var connectionString = "url=https://mycrmsystem/myorg; Username=myusername; Password=mypassword; Domain=mydomain; authtype=AD";

 

The other feature is the .IsReady property, if this is set to true the connection has been successful and can be used for further processing, otherwise the properties .LastCRMError and .LastCRMException can be checked to see what went wrong.

 

 

Recovering the licence key in a Dynamics 365 on-premise system.

Needing to verify that the correct licence key had been used for a Dynamics 365 on-premise upgrade I realised that the Deployment Manager will allow you to change the key that’s being used, but it won’t allow you to see the current key.

Being on-premise it made life slightly easier as all I had to do was break out the SQL Management studio and run the following query.

select NVarCharColumn from MSCRM_CONFIG.dbo.ConfigSettingsProperties where ColumnName='LicenseKeyV8RTM'

Of course you will need suitable rights to the MSCRM_CONFIG database to be able to run this query.

Compare Guids in JavaScript

One of the problems with Guids in JavaScript is they can come in a few different formats depending on where you get them from, and as JavaScript doesn’t have a dedicated Guid data type like other languages such as C# it can make comparing them tricky. 

Examples of the same Guid are:

9D6FF5B4-C4C2-E511-8108-1458D043F638

{9D6FF5B4-C4C2-E511-8108-1458D043F638}

9d6ff5b4-c4c2-e511-8108-1458d043f638

{9d6ff5b4-c4c2-e511-8108-1458d043f638}

Some will have {} some will be in uppercase, and some in lower which makes doing a comparison between them tricky, especially as you are effectively doing a simple text comparison.

Usign a little regex a simple function like this can be used.

function CompareGuid(guid1,guid2){
  If(guid1.replace(/[{}]/g,””).toLowerCase()==guid2.replace(/[{}]/g,””).toLowerCase())
     return true;
  }
  return false;
}

 

The functions takes two Guids and performs a text comparison between them, but first it removes the {} and converts them to lower case before comparing.

Changing the colour of a windows title bar in a universal application

By default when creating a blank Universal Windows application the basic window style is a white title bar on a white window.

By adding a few lines in the OnLaunched function the title bar can be coloured easily.

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
            var appView = Windows.UI.ViewManagement.ApplicationView.GetForCurrentView();
            appView.TitleBar.BackgroundColor = Colors.LightBlue;
            appView.TitleBar.ButtonBackgroundColor = Colors.LightBlue;
            appView.TitleBar.ForegroundColor = Colors.White;
            appView.TitleBar.ButtonForegroundColor = Colors.White;
            appView.Title = "Title Text";

 

The example above will show a Light blue title bar with White text.

 

Preventing infinite loops in CRM2013/2015/2016 plugins

Imagine the scenario where you have a plugin that executes when an entity is updated. We will call this entity A, and the plugin updates entity B. Not usually a problem, but to make things more interesting we have a plugin on entity B that fires an update back to entity A. This then tries to execute the plugin again and this updates B which updates A again and it causes the plugins to fail with an infinite loop.

Good system design can often get around this and 99% of the time you wont have to worry about it, but for the remaining 1% then IPluginExecutionContext.Depth property comes in very useful.

This property shows the number of times the plugin has been called during the current execution and if it is more than 8 (setting WorkflowSettings.MaxDepth can be changed) the execution fails as the system considers that an infinite loop has occurred.

So in the first example entity A is updated and the plugin executes (Depth=1), B is updated and the other plugin updates, and A is updated again. Our plugin fires again (Depth=2) and B is updated, the other plugin fires and updates A. Our plugin fires again (Depth=3) and so on.

public class SampleOnCreate : IPlugin
{
	public void Execute(IServiceProvider serviceProvider)
	{
		Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));
		IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
		IOrganizationService _service = serviceFactory.CreateOrganizationService(context.UserId);
		if (context.Depth > 1) { return; } // only fire once
		// do stuff here
	}
}

 

For most instances if you exit the plugin on context.Depth>1 will stop it running more than once from the main calling entity, and if you want it to be executed by updating entity A which then calls entity B then checking for context.Depth>2 will work, although of course actual code will depend on your requirements.

Have you lost your security icon in CRM 2016?

After recently upgrading a CRM2013 test system in one of my VMs I discovered that I couldnt change any security settings as the option in Settings had completely disappeared. I thought for a while I was going mad, but no it should definitely have been there.

It turns out the fix was really easy to do:

First create a new solution, and add the sitemap to it.

Export it.

Open the zip file and pull out the customizations.xml file and open it in your favourite editor.

Search for the file for Group Id="System_Setting"

<SubArea Id="nav_administration" ResourceId="Homepage_Administration" DescriptionResourceId="Administration_SubArea_Description" Icon="/_imgs/ico_18_administration.gif" Url="/tools/Admin/admin.aspx" AvailableOffline="false" />
<SubArea Id="nav_security" ResourceId="AdminSecurity_SubArea_Title" DescriptionResourceId="AdminSecurity_SubArea_Description" Icon="/_imgs/area/Security_32.png" Url="/tools/AdminSecurity/adminsecurity_area.aspx" AvailableOffline="false" />
<SubArea Id="nav_datamanagement" ResourceId="Homepage_DataManagement" DescriptionResourceId="DataManagement_SubArea_Description" Icon="/_imgs/ico_18_datamanagement.gif" Url="/tools/DataManagement/datamanagement.aspx" AvailableOffline="false" />

 

And between the nav_administration and nav_datamanagement items insert the highlighted block.

Save the file.

Insert the file back into the zip file.

Reimport the solution and publish it.

It might be best to do this out of hours if its a production system as I had to perform an IISRESET before the icon came back for me.