In D365 Customer Service, Macros are to be used by agents to perform a set of sequential actions that enable users to perform their daily operations efficiently in a fast and process-compliant manner. Macros can also be reused with different sessions based on the context parameters that are specific to the session.   

The following screenshot shows a macro which has been used to resolve a case.

We have an interesting use case, wherein we need to migrate hundreds of macros from an external system to Dynamics 365 and assign it to respective teams. As they can’t be migrated to Dynamics 365 using connectors, we had to come up with idea which helps us create the macros in bulk. Here is how we achieved it. 

  1. Add a sample macro to a solution and export the solution
    Export the solution and with the sample macro and extract it to see the contents of it. 

Customizations.xml

Solution.xml

2. Prepare macros.json file

The macros.json file contains details of the macros to be created as shown below.  

3. Edit solution files using Python script 

We need to run a Python script to edit the solution files to add the macros with schema validations. Following are the Python code blocks to be run in the same order. 

Code snippet to import required libraries

Code snippet to load the sample macro & the input macros json files

Code snippet to edit the customizations & solution xml files 

def editCustomizationsFile(id,macroName,MacroFileName):
tree = ET.parse(‘./macros_1_0_0_2/customizations.xml’)
root = tree.getroot() 

c = root.find(“.//Workflows”)
id = f{{{id}}}
request = ET.XML(f”’

<Workflow WorkflowId=”{id}” Name=”{macroName}” Description=”{macroName}“>
<JsonFileName>/Workflows/
{MacroFileName}.json</JsonFileName>
    <Type>1</Type>
    <Subprocess>0</Subprocess>
    <Category>9000</Category>
    <Mode>0</Mode>
    <Scope>4</Scope>
    <OnDemand>0</OnDemand>
    <TriggerOnCreate>0</TriggerOnCreate>
    <TriggerOnDelete>0</TriggerOnDelete>
    <AsyncAutodelete>0</AsyncAutodelete>
    <SyncWorkflowLogOnFailure>0</SyncWorkflowLogOnFailure>
<StateCode>0</StateCode>
    <StatusCode>1</StatusCode>
    <RunAs>1</RunAs>
    <IsTransacted>1</IsTransacted>
    <IntroducedVersion>1.0</IntroducedVersion>
<IsCustomizable>1</IsCustomizable>
<BusinessProcessType>0</BusinessProcessType>
    <IsCustomProcessingStepAllowedForOtherPublishers>1
</IsCustomProcessingStepAllowedForOtherPublishers>

    <PrimaryEntity>SystemUser</PrimaryEntity>
    <LocalizedNames>
    <LocalizedName languagecode=”1033″ description=”{macroName}” />
    </LocalizedNames>
    <Descriptions>
      <Description languagecode=”1033″ description=”{macroName}” />
    </Descriptions>
    </Workflow> 

  ”’)
c.append(request)
print(ET.tostring(root, encoding=‘unicode’))
tree.write(‘./macros_1_0_0_2/customizations.xml’ 

def editSolutionFile(id):
#id should be lower case
tree = ET.parse(‘./macros_1_0_0_2/solution.xml’)
root = tree.getroot() 

c = root.find(“.//RootComponents”)
id = f{{{id}}} 

request = ET.XML(f”’ <RootComponent type=”29″ id=”{id}” behavior=”0″ />”’)
c.append(request)
# print(ET.tostring(root, encoding=’unicode’)) tree.write(‘./macros_1_0_0_2/solution.xml’ 

def editSolutionFile(id):
#id should be lower case
tree = ET.parse(‘./macros_1_0_0_2/solution.xml’)
root = tree.getroot() 

c = root.find(“.//RootComponents”)
id = f{{{id}}} 

request = ET.XML(f”’ <RootComponent type=”29″ id=”{id}” behavior=”0″ />”’)
c.append(request)
# print(ET.tostring(root, encoding=’unicode’)) tree.write(‘./macros_1_0_0_2/solution.xml’ 

Code snippet to generate macro definition json files 

for item in macros:
    actions = {}
    macroid = str(uuid.uuid4())
    macroName = “”
    MacroFileName = “”
    if item[“type”] ==“set_priorty_team_openEmail”:
          # Macro Title 1 – Set priorty, assigne case to team, And open an email with template
       macroName = item[“title”]
       Update_an_existing_record = {
             “type”: “Update_Record”,
             “inputs”: { 
                   “EntityId”: “${anchor.incidentid}“,
                   “EntityName”: “incident”,
                   “Custom_Array”: [
                               {
                                      “Name”: “prioritycode”,
                                     “Value”:{“High”: 1, “Normal”: 2, “Low”:
3}.get(item[“priority”], 2) # default set to normal
                               },
                               {
                                     “Name”: “ownerid@odata.bind”,
                                     “Value”: “/teams(“+item[“Team_id”]+“)”
                               },
                         ]
                    },
                   “runAfter”: {}
              }
       actions[“Update_an_existing_record”] = Update_an_existing_record
Save_the_record= {
                                           “type”: “Save”,
                                           “runAfter”: {
                                                 “Update_an_existing_record”: [
                                                             “Succeeded”
                                                 ]
                                       }
                               }
       actions[“Save_the_record”] = Save_the_record
# Build email macro that will always run after save action
Open_an_email_form_with_predefined_template = {
“type”: “Draft_Email”,
“inputs”: {
“EntityId”: “${Session.AnchorTab.entityId},        
“EntityName”: “incident”,
“PartyListName”: “example@orgname.org”,
                                                       “TemplateId”: item[“Email_Template_ID”]
                               },
                               “runAfter”: {
                                     “Save_the_record”: [
                                            “Succeeded”
                                        ]
                               }
}
 This_macro_will_autofill_form_fields_based_on_the_information_you_provide= {
                  “type”: “update_form_attributes”,
                  “inputs”: {
                         “EntityName”: “email”,
                         “Custom_Array”: [
                                {
                                    “Name”: “to”,
                                    “Value”: “[{\”id\”:\”{anchor._customerid_value}\”,\”type\”:\”{anchor._customerid_value@Microsoft.Dynamics.CRM.lookuplogicalname}\”,\”name\”:\”{anchor._customerid_value@OData.Community.Display.V1.FormattedValue}\”}]”
},
                                        {
                                              “Name”: “regardingobjectid”,
                                              “Value”: “[{\”id\”: \”{anchor.incidentid}\”,\”name\”: \”{anchor.title}\”,\”entitytype\”: \”incident\”}]”
                                              }
                                  ] 

                        },
                        “runAfter”: {
                                “Open_an_email_form_with_predefined_template”: [ 
                                                                 “Succeeded”
                                                 ]
                                        }
                           } actions[“This_macro_will_autofill_form_fields_based_on_the_information_you_provide”] = This_macro_will_autofill_form_fields_based_on_the_information_you_provide actions[“Open_an_email_form_with_predefined_template”] = Open_an_email_form_with_predefined_template
elif item[“type”] ==“resolve_case”: 

            # Macro Title 2 – Resolve a case
            macroName = item[“title”]
            Resolve_Case = {
            “type”: “Resolve_Case”,
            “inputs”: {
                        “Resolution”: “Resolve This Case”,
                        “BillableTime”: “0”,
                        “ResolutionTypeCode” : “5”,
                        “IncidentId”: “${anchor.incidentid}
                },
                 “runAfter”: {}
                }
               actions[“Resolve_Case”] = Resolve_Case
               elif item[“type”] ==“set_state_status”:
  # Macro Title 3 – Make a ticket Active and waiting for details
               macroName = item[“title”]
               Update_an_existing_record = {
                                           “type”: “Update_Record”,
                                           “inputs”: {
                                                 “EntityId”: “${anchor.incidentid},
                                                 “EntityName”: “incident”,
                                                 “Custom_Array”: [
                                                         {
                                                                     “Name”: “statecode”, # making ticket active
                                                                     “Value”:{“Active”: 1, “Resolved”: 2, “Canceled”: 3}.get(item[“state”], 1) # Here setting to “resolved” doesn’t resolve the case – you need to use resolve action only
                                             },
                                             {
“Name”: “statuscode”,
“Value”:{“In Progress”: 1, “On Hold”: 2, “Waiting for Details”: 3, “Researching”: 4, “Problem Solved”: 5, “Canceled”: 6, “Information Provided”: 1000, “Merged”: 2000}.get(item[“state”], 2)
                                             }
                                  ]
                       },
“runAfter”: {}
                       }
actions[“Update_an_existing_record”] = Update_an_existing_record
                 Save_the_record= {
“type”: “Save”,
“runAfter”: {
“Update_an_existing_record”: [
“Succeeded” 

                           ]
                       }
                 }

        actions[“Save_the_record”] = Save_the_record

MacroFileName = macroName.replace(” “,“”)+“-“+macroid.upper()
      boilerPlate[“properties”][“definition”][“actions”] = actions
      editSolutionFile(macroid)
      editCustomizationsFile(macroid,macroName,MacroFileName)

with
open(“./macros_1_0_0_2/Workflows/”+MacroFileName+“.json”, ‘w’) as outfile:
             json.dump(boilerPlate, outfile) 

 

Import the modified solution 

Compress the solution files and import the zipped solution Macros_edited.zip in the target environment as shown below. 

After importing the solution in target environment, we can see the macros created as shown below.

We are in the process of creating more innovative solutions. Visit our other blogs on D365 for more case studies.