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.
204 stars 65 forks source link

Interesting error when using with ClientId #506

Closed cT-m00cat closed 1 year ago

cT-m00cat commented 1 year ago

I am using Set-CrmRecord to update the organization entity from a JSON file.. after a few false starts (mainly because JSON doesn't have a datetime data type) I got my script doing what I needed it to do.. the next step was to convert this into an Azure DevOps pipeline task as ultimately I need to be able to spin up an environment and configure it identically to the previous environments..... ALM..

I found when I do this in the context of an "application user" (with System Administrator role) using ClientId and ClientSecret rather than as my "sys admin" user I get an error... I was able to reproduce this locally when logging in using -OAuthClientId option... so it wasn't only occuring in a pipeline.

Below is the error I am getting... I thought, at first, that graphUser was a setting I had not seen and looked for it in my JSON.. it's not there.. and I am a little confused as to what is happening exactly and what the error is trying to tell me.

Expected value between 1 and 1 inclusive. => Expected value between 1 and 1 inclusive. => System.ArgumentOutOfRangeException: Expected value between 1 and 1 inclusive.
Parameter name: graphUsers

Would appreciate some assistance... apologies if this is not an issue and I am being dense here...

Also a big thanks on New-CrmOptionSetValue <-- this drove me nuts until I realised I can use this cmdlet... here's a tip for anyone coming across this on github in the future: RTFM

cT-m00cat commented 1 year ago

I've done a bit of investigating and what I think the root cause is the fact I am using an application user. I know there are some things MS don't allow non-interactive users to do. I suspect some of the settings in the organization entity are protected like this and thus I am experiencing the issue. Am I able to take advantage of user impersonation?

cT-m00cat commented 1 year ago

I've done yet more digging... is there a way for me to pass the CrmServiceClient.CallerId Property when initiating a connection? This would enable me to pass in the GuID of a user to impersonate.

seanmcne commented 1 year ago

That error is interesting - you should be able to login fine with a ClientId and secret (as long as the appuser has a role which grants it permissions to execute various api calls). The error though makes me wonder if your app/principal has the right permissions set on it? Here's an example - the user shouldn't require dev ops impersonation to login, but the three with green arrows should be set to ensure the ability to login/connect as an app user w/ a secret or certificate. image

As for the impersonation question there is a cmdlet: Set-CrmConnectionCallerId which changes the callerid for the connection so you can impersonate another userid.

https://github.com/seanmcne/Microsoft.Xrm.Data.PowerShell/blob/64e0f64016dba9b73a1c4b99ac9fa0ef909e1dfc/Microsoft.Xrm.Data.PowerShell/Microsoft.Xrm.Data.PowerShell.psm1#L4640

cT-m00cat commented 1 year ago

oh I don't have the powerapps runtime service user_impersonation set.. will do that.. have been speaking to some of our devs.. and it seems there is actually some stuff (according to them) that cannot be altered by a non interactive "app user" - apparently we've run into this in the past and they had to resort to user impersonation code..

I was not aware of this cmdlet.. thanks Sean..

Please close this. Regards Dan

seanmcne commented 1 year ago

Thanks Dan, let me know what they're finding as I'm curious if it's something with a related downstream api call that requires additional permissions (like mailbox/email related settings) or other things. We definitely want to encourage the use of a service principal for any headless/non interactive users otherwise it leads to less than ideal practices (like using an actual user w/ a password and no MFA, etc - which is not good).

Also, curious if the error you're getting is on login or if it's an api call after you login - you should be able to successfully login using something similar to the command below. I've also tested this and am able to successfully connect and issue a invoke-crmwhoami and other commands - if you're getting the exception when connecting (connect-crmonline) then it's most likely related to an azureAd configuration or permission but using a tool like fiddler (classic) can help trace out what http calls are leading to a failure.

Connect-CrmOnline -ServerUrl "https://contoso.crm.dynamics.com" -ClientSecret "T_DA0EY+DdfaJFH3/DFLK@QoFbY" -OAuthClientId "812fad6d-4727-44f5-b02e-f35d74efb78e"
cT-m00cat commented 1 year ago

that's exactly how I login... the exception is when I attempt to Set-CrmRecord on organisation entity and it ONLY occurs if I am logged in as the app user.. when I login as "me" it works fine. I tried setting the CallerID to my aaduser GuID and then running the Set-CrmRecord while logged in as the app user and am still getting the error

I can confirm I have the exact same API permissions on the application in question., Here's what I do..

I have a json file of System Settings found in organization entity.. I have a separate list of the fields that are datetime or optionsetvalues.. I convert the json, get it into hashtable, loop thru it.. compare the names against the list of optionsetvalues and datetime fields.. if any of the settings in my json match they are typed correctly ([datetime]$myvalue or by New-CrmOptionSetValue - now its all in the the hashtable i use set-crmRecord to update the organisation entity with: Set-CrmRecord -EntityLogicalName organization -Id (Invoke-CrmWhoAmI).OrganizationId -Fields $hashtable

The thing that makes me think there's something else going on is that when I use MY account.. this works just fine. No errors and the entity/table is updated accordingly.

Here's the full error:

Modules\Microsoft.Xrm.Data.Powershell\2.8.14\Microsoft.Xrm.Data.Powershell.psm1:910 char:3
+         throw LastCrmConnectorException($conn)
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (************ Fa...al value was 0.:String) [], RuntimeException
    + FullyQualifiedErrorId : ************ FaultException`1 - Update : Updating organization : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |=> Expected value   
   between 1 and 1 inclusive.
    Expected value between 1 and 1 inclusive. => Expected value between 1 and 1 inclusive. => System.ArgumentOutOfRangeException: Expected value betwee  
   n 1 and 1 inclusive.
Parameter name: graphUsers
    Actual value was 0.[TerminalFailure] Failed to Execute Command - Update : RequestID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX : Updating organization :   
   xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx duration=00:00:00.2862927 ExceptionMessage = Expected value between 1 and 1 inclusive.
    Expected value between 1 and 1 inclusive. => Expected value between 1 and 1 inclusive. => System.ArgumentOutOfRangeException: Expected value betwee  
   n 1 and 1 inclusive.
Parameter name: graphUsers
Actual value was 0.
cT-m00cat commented 1 year ago

Here it runs when I am logged in as a System Administrator - and there is no error.. the entity is updated.

image

Here I login using the OAuthClientId:

image

And now it will do this:

image

The only difference being that I am now logged in as the App User.. which I can confirm has System Administrator role and has the API permissions required. I do pipeline deployments of solutions and PAPortals with it. It has the correct permissions.

If I do: Set-CrmConnectionCallerId -CallerId (after connecting but before Set-CrmRecord) when logged in with clientId the result is exactly the same.

cT-m00cat commented 1 year ago

Fiddler shows this in the error returned...

image

Which is odd. The AppUser is a system administrator. I am a system administrator. It works with my account but not with the app - and appears to be an issue with ownership.

Do you have any thoughts or suggestions?

seanmcne commented 1 year ago

That is odd - do you have a list of the attributes your updating so I can try the same?

cT-m00cat commented 1 year ago

Yes, I thought it really strange too... here's the json

Organisation_Settings.txt

I had to change the file extension from json to txt... but you'll quickly see it's just a JSON file of the attributes listed as "Editable" at the docs.microsoft.com page for the organisatoin entity

cT-m00cat commented 1 year ago

OrganisationSettings.ps1.txt

Here's the script... As you can see it is relatively simple. If I can get this issue with not being able to perform the task as the application user figured out... I am going to put this into an Azure DevOps task so we can spin up an environment and apply the settings we use as "default" in seconds.. it will save us a lot of time...

Again, I've added the .txt extension to the powershell script Cheers

cT-m00cat commented 1 year ago

org_dateTimeFields.txt org_picklistFields.txt

the picklist and datetime field lists that I use...

seanmcne commented 1 year ago

I don't think these are valid for update and I'd expect a failure

I won't list them all as I don't know each off hand but many of the organization picklists are also valid for read, but typically not valid for update (it might not be all of them, but many I'm seeing may not be updatable).

Also, orgdborgsettings have to be updated in a strange way where you append each fragment one-by-one. I'd be surprised if the script ran without issue when logged in as a normal user just because at least some of these fields should not be updatable (but would be readable).

seanmcne commented 1 year ago

FYI - I did just execute this in my environment when logged in as a tenant global admin/org admin and the result for me was a failure but regarding a specific feature. I'll try to update each field one-by-one to see what errors I get as a 'admin user' and try the same for an app user.

seanmcne commented 1 year ago

I got two different results from when I submitted the hashtable (which gave me an error regarding relationship management) whereas when I ran the fields one-by-one I only got one exception using the same input files & script logic except updating them each at a time: image

Exception produced when submitting the entire hashtable (my app user is not a global admin): image

Exceptions submitting each setting individually - it applied many of them without issue and these each failed for various reasons: RequireApprovalForQueueEmail image isRelationshipInsightsEnabled image featureset - this is another one you'll want to be careful with setting by script or being specific in what you're appending vs. overwriting image requireApprovalForUserEmail image

I think for these types of settings you'd be better off submitting them one by one as there is a bunch of logic behind some of these fields. Orgdborgsettings being was one which was/is 'special' in terms of how you interact with it - it looks like it might be accepting more than one setting at a time now, which would be different than it was in the past, so this may be less of a concern now - I would recommend it's actually overwriting the whole thing and I'd give similar advise as the featureset attribute. It is likely there are others with some unique behaviors. If you break them up into individual updates it might be easier to isolate when things go wrong or track that things are being set as you'd expect.

Here is what I used to update each attribute by itself:

$hashtable.GetEnumerator() | ForEach-Object {
try{
    Write-Host "Updating $($_.Key) to $($_.Value)"
    Set-CrmRecord -EntityLogicalName organization -Id $orgid -Fields @{$_.Key=$_.Value}
    #$_.Key - $_.Value
    }
    catch{
        write-output $_
    }
}

Hopefully this is somewhat helpful in terms of offering better reliability and also narrowing down what's producing the error you're seeing!

cT-m00cat commented 1 year ago

Sean.. you are amazing and an absolute star. This is so helpful and the knowledge around the way orgdborgsettings works is invaluable to me. I benefit from your experience. I hadn't even seen Dynamics 11 months ago... So, I am still "wet behind the ears" so to speak. I didn't know about Orgdborgsettings being special - thanks a lot for the assistance. This is really helpful and will enable me to finish the task at hand.

seanmcne commented 1 year ago

That is mighty kind of you! I am happy to hear I could help make things a little easier! The organization entity has a lot of special stuff going on as many of the attributes configure how the instance works and some of those settings are 'unique' and orgdborgsettings is one of those - those typically shouldn't need to be programmatically set by you as a customer or partner unless it's for a very specific feature/capability that is pre-release or something that is non-standard and specific (there are a couple I could see around email handling, etc) but you still want to be careful with setting too many automatically unless you are specifically tracking the need (in case it changes in the future, etc).

PS: welcome back to the Dynamics, Dataverse, and PowerApps world!