Business Process Flow migration

Hello everyone,

Today, I'd like to focus on the Business Process flow part of the Dynamics 365 CRM.

A little bit of context here, 2 weeks ago, I had the huge chance to attend the Global MVP Summit in Seattle/Redmond, and during some exchanges with other CRM MVPs, I heard a great idea coming from Gus Gonzalez : "What if we could create a tool to migrate records to the same BPF but different stage or to another BPF and selected stage ?".
I didn't wait long before I initiated the Visual Studio project on my laptop and started to investigate the possibilities ! :-)

That said, I've the pleasure to introduce you our latest XrmToolBox plugin :

BPF Manager

What can do the plugin ? (for now!)

For now there are 2 major possibilities :

  • Migrate records to the same BPF but different stage
  • Migrate records to a different BPF and selected stage

Sounds easy, but since the version 8.2 (it's been a while now but it can't hurt to remind the current process), the way BPF are handled with records and users was modified heavily.
The interesting functional part here is that a BPF instance is created for each record and user.
Let's do some maths : 100 users for 400 records = 40 000 BPF instances to manage.

Technical side

As said above, each user have an instance specific of the BPF for each records. (as explained here : Link)

Example :
I just created my latest opportunity and out of my 150 users, only 10 accessed the record. At this point, in the BPF instances, we will have only 10 rows out of the 150. Until user access the specific record, the BPF will not show up on the database.

As we want to "force" the operation to be done programmatically here, we will focus on the two main parts of the process:

  1. SetProcessRequest which will allow you to instanciate the BPF for any users.
  2. Update the BPF row linked to the wanted record to specify the active stage and the traversedpath

Instanciate the new BPF

In order to instanciate a new business process flow, we will need to use the SetProcessRequest message from the SDK.
Here is a sample of the code :

// Create the instance of the BPF on the record
SetProcessRequest setProcReq = new SetProcessRequest  
{
    Target = record.ToEntityReference(),
    NewProcess = new EntityReference(BPF.LogicalName, BPF.Id)
};

This message will create a new instance of the BPF for the user who is used during the Execute of that request.

  • Target is the EntityReference of the record you want to affect (opportunity, lead or any other record entity with BPF's)
  • NewProcess will allow the CRM to create a new instance of the BPF (if not existing) for the specified record.

Once our BPF is created properly on our records, we now want to choose which is the targeted stage, by default when you instanciate a new BPF, it will be set to the first stage (which make sense !).

Choose your targeted stage

In order to have the choice about the stage you want to enable during the BPF instanciation, you have the possibility to configure 2 attributes which will make the trick.

traversedpath and activestageid are the ones.

  • ActiveStageId is a the EntityReference to the stage of the previously instance BPF you want the record to be set to.
  • TraversedPath is a string variable which contains all stages guid's needed to access the wanted one (ex : GuidStage1,GuidStage2 for a target of stage 2), you will see how to get it below.

Once you have those 2 parameters, a simple Update message will do the job :

// Update stage of a record
var bpfInstance = new Entity()  
{
    LogicalName = targetedBPF.LogicalName,
    Id = targetedBPF.Id
};
bpfInstance["activestageid"] = new EntityReference(stageId.LogicalName, stageId.Id);  
bpfInstance["traversedpath"] = traversedpath;

_service.Update(bpfInstance);  

Let's have a closer look to the traversedpath variable.
As said, we want here to retrieve all stage guids which are before the targeted one on the BPF definition.

List<string> traversedpath = new List<string>();  
var activePathRequest = new RetrieveActivePathRequest  
{
    ProcessInstanceId = targetedBPF.Id
};

var activePathResponse = (RetrieveActivePathResponse)this.Service.Execute(activePathRequest);

var stageDefinitions =  
    ((EntityCollection)activePathResponse.Results.Values.FirstOrDefault())?.Entities;

foreach (var path in stageDefinitions)  
{
    traversedpath.Add(path.Id.ToString());

    if (path.Attributes["stagename"].ToString() == targetStage)
        break;
}

// Desired result : 
var resultTraversedPath = String.Join(",",traversedpath);  

Here is what it is done here :

  1. Query a record which have the wanted BPF instanced (mandatory to have at least one, since from querying the BPF directly, you can't know what is the stage order)
  2. Loop through all stage of the BPF and store them in a list
  3. As soon as I reached my targeted stage, I can't break the loop (no need to use extra resources for nothing).
Let's roll

Now that you have the method to set a BPF instance and a way to set the wanted stage, you can now run your script.
One more thing, we mentioned that it was based on user scope.
The last needed trick here is to execute the 2 steps for each users of your instance so they are all aligned.
If you just run the script with your credentials, you will be the only one to see the new BPF instance on your record.

Known limit

Unfortunately, I'm facing one major limit with that method.
If you have a BPF A already instanced but not active, and a BPF B active, the current process won't allow the BPF A to be shown on the form.

Hope this help,
Happy CRM'in !

Clement

Dynamics 365 CRM & Power Platform addict.