Power Automate: (Experiment) Migrate Dataverse Legacy to Latest Dataverse Connector

Thinking about how to migrate Dataverse Legacy to the new Dataverse connector as the depreciation date coming? Here is how I migrate it (remembered to always back up your flows before trying it and do it with your own risk).

The easiest way to know the difference is by creating the same exact flow with all those actions needed (Create, Update, Delete, Get By Row Id, and List):

Legacy vs latest Dataverse flow

Below is the legacy flow JSON:

{
  "properties": {
    "connectionReferences": {
      "shared_commondataservice": {
        "runtimeSource": "invoker",
        "connection": {
          "connectionReferenceLogicalName": "tmy_sharedcommondataservice_19e76"
        },
        "api": {
          "name": "shared_commondataservice"
        }
      }
    },
    "definition": {
      "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "$connections": {
          "defaultValue": {},
          "type": "Object"
        },
        "$authentication": {
          "defaultValue": {},
          "type": "SecureObject"
        }
      },
      "triggers": {
        "manual": {
          "metadata": {
            "operationMetadataId": "1740d511-eb9d-43c2-bb25-4e3e08d87463"
          },
          "type": "Request",
          "kind": "PowerApp",
          "inputs": {
            "schema": {
              "type": "object",
              "properties": {},
              "required": []
            }
          }
        }
      },
      "actions": {
        "Add_a_new_row_(legacy)": {
          "runAfter": {},
          "metadata": {
            "operationMetadataId": "f97ee139-be94-4e78-b8b6-639b0a855684"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataservice",
              "operationId": "PostItem_V2",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataservice"
            },
            "parameters": {
              "dataset": "https://org2d56f1ac.crm5.dynamics.com",
              "table": "contacts",
              "item/lastname": "Temmy",
              "item/_parentcustomerid_type": "",
              "item/isbackofficecustomer": false,
              "item/adx_confirmremovepassword": false,
              "item/creditonhold": false,
              "item/msdyn_disablewebtracking": false,
              "item/donotbulkemail": false,
              "item/donotbulkpostalmail": false,
              "item/donotemail": false,
              "item/donotfax": false,
              "item/donotpostalmail": false,
              "item/donotphone": false,
              "item/adx_identity_emailaddress1confirmed": false,
              "item/followemail": true,
              "item/msdyn_isminor": false,
              "item/msdyn_isminorwithparentalconsent": false,
              "item/adx_identity_locallogindisabled": false,
              "item/adx_identity_lockoutenabled": false,
              "item/adx_identity_logonenabled": false,
              "item/marketingonly": false,
              "item/adx_identity_mobilephoneconfirmed": false,
              "item/participatesinworkflow": false,
              "item/adx_profilealert": false,
              "item/adx_profileisanonymous": false,
              "item/donotsendmm": false,
              "item/adx_identity_twofactorenabled": false,
              "item/_ownerid_type": ""
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        },
        "Update_a_row_(legacy)": {
          "runAfter": {
            "Add_a_new_row_(legacy)": [
              "Succeeded"
            ]
          },
          "metadata": {
            "operationMetadataId": "a2759590-93b2-4318-aa35-372de4f403f5"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataservice",
              "operationId": "PatchItem_V2",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataservice"
            },
            "parameters": {
              "dataset": "https://org2d56f1ac.crm5.dynamics.com",
              "table": "contacts",
              "id": "a56a4006-9a0e-4e4f-8d86-ba1b2778f584",
              "item/lastname": "Temmy",
              "item/_parentcustomerid_type": "",
              "item/_ownerid_type": ""
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        },
        "Delete_a_row": {
          "runAfter": {
            "Update_a_row_(legacy)": [
              "Succeeded"
            ]
          },
          "metadata": {
            "operationMetadataId": "4d121d02-db4f-4c3b-b1de-d021ca10bfb8"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataservice",
              "operationId": "DeleteItem",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataservice"
            },
            "parameters": {
              "dataset": "https://org2d56f1ac.crm5.dynamics.com",
              "table": "contacts",
              "id": "a56a4006-9a0e-4e4f-8d86-ba1b2778f584"
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        },
        "Get_row_(legacy)": {
          "runAfter": {
            "Delete_a_row": [
              "Succeeded"
            ]
          },
          "metadata": {
            "operationMetadataId": "c6f306bf-99b2-4d20-b66b-7a98c0b045a1"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataservice",
              "operationId": "GetItem_V2",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataservice"
            },
            "parameters": {
              "dataset": "https://org2d56f1ac.crm5.dynamics.com",
              "table": "contacts",
              "id": "a56a4006-9a0e-4e4f-8d86-ba1b2778f584"
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        },
        "List_rows_(legacy)": {
          "runAfter": {
            "Get_row_(legacy)": [
              "Succeeded"
            ]
          },
          "metadata": {
            "operationMetadataId": "0a7d3e04-9e1e-450a-b828-de565701fc3e"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataservice",
              "operationId": "GetItems_V2",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataservice"
            },
            "parameters": {
              "dataset": "https://org2d56f1ac.crm5.dynamics.com",
              "table": "contacts",
              "$top": 1
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        }
      }
    },
    "templateName": ""
  },
  "schemaVersion": "1.0.0.0"
}

And here is the new flow JSON:

{
  "properties": {
    "connectionReferences": {
      "shared_commondataserviceforapps": {
        "runtimeSource": "embedded",
        "connection": {
          "connectionReferenceLogicalName": "tmy_sharedcommondataserviceforapps_db083"
        },
        "api": {
          "name": "shared_commondataserviceforapps"
        }
      }
    },
    "definition": {
      "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "$connections": {
          "defaultValue": {},
          "type": "Object"
        },
        "$authentication": {
          "defaultValue": {},
          "type": "SecureObject"
        }
      },
      "triggers": {
        "manual": {
          "metadata": {
            "operationMetadataId": "c675d7b4-f16a-47d4-ba50-6477c29b7d6b"
          },
          "type": "Request",
          "kind": "PowerApp",
          "inputs": {
            "schema": {
              "type": "object",
              "properties": {},
              "required": []
            }
          }
        }
      },
      "actions": {
        "Add_a_new_row": {
          "runAfter": {},
          "metadata": {
            "operationMetadataId": "96efb6ea-fa12-4121-a23b-545eadf1f831"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "CreateRecord",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "entityName": "contacts",
              "item/lastname": "Temmy Raharjo"
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        },
        "Update_a_row": {
          "runAfter": {
            "Add_a_new_row": [
              "Succeeded"
            ]
          },
          "metadata": {
            "operationMetadataId": "71366ba0-1754-4c34-9d70-22cbcc594cb9"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "UpdateRecord",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "entityName": "contacts",
              "recordId": "a56a4006-9a0e-4e4f-8d86-ba1b2778f584",
              "item/lastname": "Temmy"
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        },
        "Delete_a_row": {
          "runAfter": {
            "Update_a_row": [
              "Succeeded"
            ]
          },
          "metadata": {
            "operationMetadataId": "efadd439-c4f5-49da-b092-68be9a781b18"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "DeleteRecord",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "entityName": "contacts",
              "recordId": "a56a4006-9a0e-4e4f-8d86-ba1b2778f584"
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        },
        "Get_a_row_by_ID": {
          "runAfter": {
            "Delete_a_row": [
              "Succeeded"
            ]
          },
          "metadata": {
            "operationMetadataId": "6e72fe43-3b72-4106-8875-61a02b6b2163"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "GetItem",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "entityName": "contacts",
              "recordId": "a56a4006-9a0e-4e4f-8d86-ba1b2778f584"
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        },
        "List_rows": {
          "runAfter": {
            "Get_a_row_by_ID": [
              "Succeeded"
            ]
          },
          "metadata": {
            "operationMetadataId": "30c1c040-20fe-41e0-ade9-56f43e8b1ae1"
          },
          "type": "OpenApiConnection",
          "inputs": {
            "host": {
              "connectionName": "shared_commondataserviceforapps",
              "operationId": "ListRecords",
              "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps"
            },
            "parameters": {
              "entityName": "contacts",
              "$top": 1
            },
            "authentication": {
              "type": "Raw",
              "value": "@json(decodeBase64(triggerOutputs().headers['X-MS-APIM-Tokens']))['$ConnectionKey']"
            }
          }
        }
      }
    },
    "templateName": ""
  },
  "schemaVersion": "1.0.0.0"
}

If you compare those 2 JSONs, you can see that the new Dataverse connector are indeed simpler. I can see the legacy connector has some unnecessary attributes (In the create action, I just add Last Name, but somehow in the legacy flows adding more attributes that I not mentioned):

Comparing new flow vs legacy flow (the red boxes is Legacy)

Here is my code to automatically migrate to the new Dataverse Action (you may be got an error when running using this tool as I’m making this one with my testing flow. So do it at your own risk):

using System.Collections;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Reflection;
using Newtonsoft.Json.Linq;

var appSettingsFilePath = Directory.GetCurrentDirectory() + "//appSettings.json";
var appSettings = JsonConvert.DeserializeObject<AppSettings>(File.ReadAllText(appSettingsFilePath));

var files = Directory.GetFiles(appSettings.WorkingDirectory, "*.*", SearchOption.AllDirectories)
    .Where(s => Path.GetExtension(s).ToLowerInvariant() == ".json");

foreach (var filePath in files)
{
    var originalText = File.ReadAllText(filePath);

    // Minifying JSON
    var obj = JsonConvert.DeserializeObject(originalText);
    var minifyJson = JsonConvert.SerializeObject(obj, Formatting.None);

    // Skip if no dataverse legacy
    if (!originalText.Contains("\"shared_commondataservice\"")) continue;
    minifyJson = minifyJson.Replace("\"shared_commondataservice\":{\"runtimeSource\":\"invoker\",", "\"shared_commondataservice\":{\"runtimeSource\":\"embedded\",");
    minifyJson = minifyJson.Replace("shared_commondataservice", "shared_commondataserviceforapps");
    minifyJson =
        minifyJson.Replace($"\"connectionReferenceLogicalName\":\"{appSettings.DataverseLegacyConnectionName}\"",
            $"\"connectionReferenceLogicalName\":\"{appSettings.DataverseConnectionName}\"");
    minifyJson = ReplaceDatasetToId(minifyJson);

    minifyJson = minifyJson.Replace("\"operationId\":\"PostItem_V2\"", "\"operationId\":\"CreateRecord\"");
    minifyJson = minifyJson.Replace("\"operationId\":\"PatchItem_V2\"", "\"operationId\":\"UpdateRecord\"");
    minifyJson = minifyJson.Replace("\"operationId\":\"DeleteItem\"", "\"operationId\":\"DeleteRecord\"");
    minifyJson = minifyJson.Replace("\"operationId\":\"GetItem_V2\"", "\"operationId\":\"GetItem\"");
    minifyJson = minifyJson.Replace("\"operationId\":\"GetItems_V2\"", "\"operationId\":\"ListRecords\"");
    obj = JsonConvert.DeserializeObject(minifyJson);

    var modifiedText = JsonConvert.SerializeObject(obj, Formatting.Indented);

    File.WriteAllText(filePath, modifiedText);
    Console.WriteLine("Replaced..");
}

string RemoveEmptyValues(string text)
{
    var splitParameters = text.Split("\"item/").ToArray();
    var result = new List<string>();
    foreach (var parameter in splitParameters)
    {
        var splitValues = parameter.Split(":");
        if (splitValues[0].StartsWith('"'))
        {
            result.Add(parameter);
            continue;
        }
        if (splitValues.Length == 2 && splitValues[1].Contains("\"\"")) continue;
        if (splitValues.Length > 2 && splitValues[1].Contains("\"\""))
        {
            splitValues[1] = splitValues[1].Replace("\"\"", "");
            var tempText = string.Join(":", splitValues.Skip(1));
            result.Add(tempText);
            continue;
        }

        result.Add("\"item/" + parameter);
    }
    var temp = string.Join("", result);
    return temp;
}

string ReplaceDatasetToId(string text)
{
    var splitParameters = text.Split("\"parameters\":{").ToArray();
    if (!splitParameters.Any(e => e.Contains("\"dataset\"") &&
                                 e.Contains("\"table\"") && e.Contains("\"id\""))) return text;

    var copySplitParameters = splitParameters.ToArray();
    for (var i = 0; i < splitParameters.Length; i++)
    {
        var parameter = splitParameters[i];
        var valid = parameter.Contains("\"dataset\"") &&
           parameter.Contains("\"table\"");
        if (!valid) continue;
        var containsId = parameter.Contains("\"id\"");
        var find = containsId ? "\"id\"" : "\"table\"";
        var ended = parameter.IndexOf(find, StringComparison.CurrentCulture);
        var findText = parameter.Substring(0, ended) + find;
        var findTable = findText.IndexOf("\"table\"", StringComparison.CurrentCulture);
        var findTableText = findText.Substring(0, findTable);

        var result = parameter.Replace(findTableText, "")
            .Replace("\"table\"", "\"entityName\"")
            .Replace("\"id\"", "\"recordId\"");
        copySplitParameters[i] = RemoveEmptyValues(result);
    }

    return string.Join("\"parameters\":{", copySplitParameters);
}


Console.ReadKey();
public class AppSettings
{
    public string DataverseLegacyConnectionName { get; set; } = null!;
    public string DataverseConnectionName { get; set; } = null!;
    public string WorkingDirectory { get; set; } = null!;
}

Once done with the code, I exported my solution where it has the Flow that I needed (remembered to also include the new Dataverse connection – you can create a flow that uses the new Dataverse connector):

Demo solution

Once it is done, export the solution to unmanaged and extract the content inside the zip (you can use right-click> Extract all on the solution zip) because later on we will import the solution again and verify:

Unzip result

Then for the Console, you need to update the appSettings.json to reflect your needs:

{
  "dataverseLegacyConnectionName": "tmy_sharedcommondataservice_19e76",
  "dataverseConnectionName": "tmy_sharedcommondataserviceforapps_db083",
  "workingDirectory": "C:\\Users\\Temmy Raharjo\\Desktop\\Flow\\Blog_1_0_0_2 - Copy"
}

Changed the connection names that you need to replace (from legacy connection to new Dataverse connection). And also you need to put your working directory. Once this is done, you can run the program and it will automatically replace the result for you!

Compare JSON result

The result:

Successfully converted + can run 😎

You can get the full solution in this GitHub folder https://github.com/temmyraharjo/dynamics-crm-samples/tree/main/src/MigrateDataverseTools.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.