fbpx
KNOWLEDGE BASE

Query Dynamics 365 Contact Audit History

We recently had an issue with our Dynamics 365 environment where the email addresses from most contacts were deleted due to a Microsoft bug with server side synchronisation.

 

Microsoft had an issue report, had identified the code issue but had not deployed any fix after a number of days and their support team were of little use: https://admin.powerplatform.microsoft.com/support/serviceHealth?serviceHealthId=DV1121532

 

In this flow we will use the Dynamics 365 API to access the audit reports of a contact record with Power Automate, detect any records where their email address was changed to null, capture the previous email address, then write this address back into Dynamics as the contacts email address again.

 

As this was just a once of issue we will use an on-demand flow in Power Automate.

 

Flow summary:

 

1. Get all contacts in Dynamics modified around the time of the Microsoft bug that do not have an email address. In our situation the bug impacted our environment on the 27th and 28th so to minimize the risk of detecting contacts that had the email address legitimately removed we also filter based on the modified date. The modifiedon lt is a date 12h in the future. The select columns does not impact on functionality it just improves the performance by limiting the amount of data that the API needs to return.

Select: emailaddress1, statecode, modifiedon, contactid, firstname, lastname

Filter: emailaddress1 eq null and modifiedon gt ‘2025-07-25T00:00:00Z’ and modifiedon lt ‘2025-08-05T00:00:00Z’

  1. Initialise a variable to track if the contact has already been updated (as we only want to update each contact once and there may be some contacts that were manually fixed or where the Microsoft bud updated them multiple times).
  1. Iterate through each of the contact records using an apply to each loop. As we are using a variable inside the Apply To each set the concurrency to 1 under settings.

4. We then get the most recent audit logs for each contact using the data from the first List Rows request. In our example we are filtering the audits by when they were created and limiting the number that are returned to 10. We are also limiting the results by updates only. The objecttypecode should be the entity you are requesting audit date for and the _objectid_value is the contacted.

 

Filter: objecttypecode eq ‘contact’ and _objectid_value eq ‘@{items(‘ApplyToEach-ContactsNoEmail’)?[‘contactid’]}’ and createdon gt ‘2025-07-25T00:00:00Z’ and createdon lt ‘2025-08-05T00:00:00Z’ and action eq 2

  1. We now need to iterate through each of the audit logs. Concurrency should also be enabled on this loop.
  1. The first step is to set the varContactUpdated bac to false, we will use this field to track if a contact has been updated or not.

7. The data returned from the audits is stored in json within the changedata field so we need to convert this into a json format within the flow so we can access the variable using the Parse JSON action. You can generate the schema by pasting in a sample of the json data from the changedata field. When you do this though you need to update the oldValue and newValue fields to also allow null values.

Schema: {

    “type”: “object”,

    “properties”: {

        “changedAttributes”: {

            “type”: “array”,

            “items”: {

                “type”: “object”,

                “properties”: {

                    “logicalName”: {

                        “type”: “string”

                    },

                    “oldValue”: {

                        “type”: [

                            “string”,

                            “null”

                        ]

                    },

                    “newValue”: {

                        “type”: [

                            “string”,

                            “null”

                        ]

                    }

                },

                “required”: [

                    “logicalName”,

                    “oldValue”,

                    “newValue”

                ]

            }

        }

    }

}

  1. We then need to check if the audit record is one of the records that was generated by the Microsoft bug and if we need to use the data from this record to update the contact in Dynamics. So that we do not need to use another loop we use the first record only from the Parse JSON action as this action is passed a single audit log and should only return one result. We are also checking to see if this contact has already been updated via the varContactUpdated variable and allowing for an empty or null string value within the expression. For our specific issue we only care of the logincalname of the record is emailaddress1 and the record did previously have a value and was changed to a null or empty value.

Fields: first(outputs(‘Parse_JSON’)?[‘body’]?[‘changedAttributes’])?[‘logicalName’]

first(outputs(‘Parse_JSON’)?[‘body’]?[‘changedAttributes’])?[‘oldValue’]

first(outputs(‘Parse_JSON’)?[‘body’]?[‘changedAttributes’])?[‘newValue’]

 

  1. If the conduction result is true we set the varContactUpdated to true so that we do not update this contach a second time and then update the record with an dataverse update row action.

 

Email field: first(outputs(‘Parse_JSON’)?[‘body’]?[‘changedAttributes’])?[‘oldValue’]

10. For testing, run the flow in a staging environment first or set the row count on the first list rows to a count of 1 to limit the number of records that are updated. Without this in place it will get 5000 rows so in production it may need to be run multiple times or you can set this to a larger value.

11. Full flow diagram:

If you have any further questions please contact our team.