seanmcne / Microsoft.Xrm.Data.PowerShell

This module uses the CRM connection from Microsoft.Xrm.Tooling.CrmConnector.Powershell and provides common functions to create, delete, query, and update data as well as functions for common tasks such as publishing, and manipulating System & CRM User Settings, etc. The module should function for both Dynamics CRM Online and On-Premise environment.
201 stars 64 forks source link

Set-CrmRecord case sensitivity ignored? #524

Closed raph91 closed 1 year ago

raph91 commented 1 year ago

Hi

I am trying to update records in dynamics 365 with Set-CrmRecord (account table). I think "Set-CrmRecord" is not case sensitive, means that if the same string is available but with another spelling, it does not update the field.

Example: Dynamics 365 field value: CoMpany1 Source field value: Company1

I can successfully update the record, but when I get it again (either with Get-CrmRecord or Get-CrmRecordsByFetch), the old value is still set. When I change the source field value to Company2, the Dynamics 365 field value changes correctly to "Company2".

So I assume "Set-CrmRecord" doesn't care about the spelling/case sensitivity and does not update the record, if the characters are equal. Could that be the case?

Thank you

seanmcne commented 1 year ago

Hi @raph91 - Set-CrmRecord will execute an update to the record regardless of what is supplied (you can even supply the same exact value with zero changes and it will set the change).

Here is an example:

$accounts=get-crmrecords -EntityLogicalName account -TopCount 5 -Fields name
$accounts.CrmRecords[0].name

image

$accounts.CrmRecords[0].accountid
$accounts.CrmRecords[0].name

image

--Update the account using the accountid and the current (identical) name
Set-CrmRecord -EntityLogicalName account -Id $accounts.CrmRecords[0].accountid -Fields @{"name"=$accounts.CrmRecords[0].name}

image


Now, as a test, let's change it using a different letter case (toUpper will turn it all caps). Then we'll re-retrieve the same exact record and read back the value to show any changes:

write-host "Currrent value:"
$accounts.CrmRecords[0].name

Write-host "change the value to: $($accounts.CrmRecords[0].name.ToUpper())"
Set-CrmRecord -EntityLogicalName account -Id $accounts.CrmRecords[0].accountid -Fields @{"name"=$accounts.CrmRecords[0].name.ToUpper()}

$changedRecord=get-crmrecord -EntityLogicalName account -Id $accounts.CrmRecords[0].accountid -Fields name

Write-Host "new value"
$changedRecord.name

image

raph91 commented 1 year ago

Hi @seanmcne

Interesting. I can confirm your steps, but my issue lies within the behavior of "Set-CrmRecord $accounts.CrmRecords[0]". When I pass the object and not specifying the entity/id, it doesn't update the field.

I think I do not need to explain your own implementation, but I add the screenshots from the Set-CrmRecords help to show what I mean: image image

First with defining entity and id:

Write-Host $accounts.CrmRecords[0].accountid
Write-Host "Current value:" $accounts.CrmRecords[0].name " (accountid:" $accounts.CrmRecords[0].accountid ")"
$accounts.CrmRecords[0].name = "pArEnT aCcOuNt NaMe"
Write-Host "New value:" $accounts.CrmRecords[0].name " (accountid:" $accounts.CrmRecords[0].accountid ")"
Set-CrmRecord -EntityLogicalName account -Id $accounts.CrmRecords[0].accountid -Fields @{"name" = $accounts.CrmRecords[0].name }
$updatedAccount = Get-CrmRecord -EntityLogicalName account -Id $accounts.CrmRecords[0].accountid -Fields name
Write-Host $updatedAccount.name

Output (working): image

Write-Host $accounts.CrmRecords[0].accountid
Write-Host "Current value:" $accounts.CrmRecords[0].name " (accountid:" $accounts.CrmRecords[0].accountid ")"
$accounts.CrmRecords[0].name = "PaReNt aCcOuNt NaMe"
Write-Host "New value:" $accounts.CrmRecords[0].name " (accountid:" $accounts.CrmRecords[0].accountid ")"

Set-CrmRecord $accounts.CrmRecords[0]

$updatedAccount = Get-CrmRecord -EntityLogicalName account -Id $accounts.CrmRecords[0].accountid -Fields name
Write-Host $updatedAccount.name

Output (not working): image

Interesstingly enough, I tried to do the same steps as described in the help, Example 4, with the same behavior: image

image

 $account = Get-CrmRecord account 9620cf26-b091-ed11-b818-00155d0105da name
  Write-Host "Old name:" $account.name
  $account.name = "Parent Account Name"
  Write-Host "New name:" $account.name
  Set-CrmRecord $account
  $newaccount = Get-CrmRecord account 9620cf26-b091-ed11-b818-00155d0105da name
  Write-Host "Updated name:" $newaccount.name

It really does only work when the new value has different characters: image

Did I miss something?

Thank you.

seanmcne commented 1 year ago

Ok thanks for clarifying the situation. When CrmRecord is used, each property on the record is compared to the original in this code: https://github.com/seanmcne/Microsoft.Xrm.Data.PowerShell/blob/2c671af6d86288d14aa29e73a2d992a8913d7e7f/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1#L744C1-L745C1

For anything which isn't a special type (which would be string, or anything that isn't specifically handled) the function does the following check to detect a difference between the two object attribute values:

$crmFieldValue -eq $originalRecord[$crmFieldKey]

It doesn't assume a string, per se, so it's just trying to see if the two object properties are equal. looking at the operator documentation from powershell -eq is insensitive and -ceq is case sensitive: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators?view=powershell-7.2#common-features

When set-crmrecord (update) is executed with the properties provodied directly there isn't any additional logic executed and whatever is provided gets sent to the API. However, when passing in an object/crmRecord there has to be several assumptions made when checking for 'what was updated' to ensure changed attributes are detected and today that logic uses an equals check.

The technical reason the update with only a case change doesn't process the difference is due to poweshell's default string comparison, when using -eq against a string it uses a case insensitive equality comparison whereas an operator like -ceq forced case sensitivity. One possible option would be to change it over to -ceq, however the -eq attribute check isn't specific to an attribute type and I think it's possible there could be some unintended consequences.

I think I can see few ways forward for this:

  1. you could update the code you have to create an object or set of attribute/value pairs and execute the update using set-crmrecord to force whatever updates you need.
  2. I could look at adding a new parameter which would force all properties to be considered 'changed' and force everything to be updated in the record. I am concerned the feature use might be limited and a developer might want to only apply the forced update parameter to a specific field and ultimately use their own attribute/value pairs to send an update anyhow.
  3. Make a logic change to test each property to see if it's a string object type and default to -ceq instead of eq - I think this could potentially impact existing users negatively.
raph91 commented 1 year ago

Thank you for the clarification, makes sense to me. I think I will stick to way 1! I appreciate your help, thank you again.