February 25, 2024

new project: QR Code Custom APIs

As I wrote in my previous post, in the recent months I created several Custom APIs and some of them are available inside my GitHub account.

The latest one is QR Code Custom APIs

Right now there is a single Custom API called GenerateQRCodeUrl

As input accepts a Url (string) and it returns the Base64 representation (string) of a PNG file, this can be used for example inside HTML content (setting the src property like data:image/png;base64, [BASE64 VALUE]) or for example to store the value as a note/attachment.

Here an example of a QR Code generated with this Custom API (it points to www.microsoft.com)


Big thanks to the project QRCoder that I am using inside this Custom API.

February 9, 2024

Impersonate Dataverse Users connected to Entra ID Groups

When a Security Group is assigned to a Dataverse Environment users need to be part of the selected Microsoft Entra ID Group (directly as members or if they are inside a group that is member of the main group) otherwise they cannot be added.

This facilitates the work of the IT department to manage who can access or not a specific environment, also a specific Team inside Dataverse can be created to map the Microsoft Entra ID Group:
Keep in mind that to a Dataverse Team you can assign Security Roles or Column Security Profiles.
Which is the disadvantage? By default users appear inside the Environment only after their first login, for some apps this is not a big deal, but for other apps users must be present before their first login.

To force the users you can use a PowerShell cmdlet (link) or a Power Automate flow (link1, link2), in this way the users will be added to the environment and they will appear inside the systemuser table.

However this approach solves half of the problem (in my opinion) because the users are added indeed to the Environment, but they are not added to the Dataverse Team connected to the Entra ID Group they are coming from, the association to the Team will happen indeed at their first login.

Let's recap: inside Entra we have the user "John Doe", it has been added inside the Entra ID Group called "Super Users" setup as Security Group for the Environment.
Inside the Environment there is the Team (Entra ID type) connected to Entra "Super Users", we forced the synchronization of the user so there is a systemuser record related to "John Doe" but this record is not associated with the Team "Super Users".

And you can think: why we should care? the problem is that the user is inside the Environment but without security roles, probably the security roles will be inherited from the Teams the user is a member of, but right now is not member of any team, they need to login first.

Again: why we should care? the problem is that if the user has no security roles, the user cannot be the owner of a record inside your Environment.

Think about a migration: you have 500 users, and 10000 account records to migrate from Ennvironment A to Environment B, these 500 users they have the right security role by the Team described above but we cannot assign them an account until they login first.

The solution is to programmatically impersonate these users and perform an action inside the Dataverse, in this way we simulate a login and all the processes associated with it (including the association to the Team)

You can probably do this with a Power Automate flow but I decided to implement this logic in C#, mainly because the WhoAmI request is not available inside the unbound actions but is one of the common requests to be executed using the C# Dataverse SDK:
QueryExpression querySystemUsers = new QueryExpression("systemuser");
querySystemUsers.ColumnSet = new ColumnSet("fullname");
EntityCollection collSystemUsers = service.RetrieveMultiple(querySystemUsers);
// Note: if your users are more than 5000 you need to do pagination!

Dictionary<Guid, string> dictErrors = new Dictionary<Guid, string>();
foreach (Entity user in collSystemUsers.Entities)
{
    try
    {
        service.CallerId = user.Id;
        service.Execute(new WhoAmIRequest());
    }
    catch (Exception ex)
    {
        dictErrors.Add(user.Id, $"{user.GetAttributeValue("fullname")} - {ex.Message}");
    }
}
As you can see inside the code, the property "CallerId" is used to impersonate the user before executing the WhoAmIRequest (probably you can use also the property "CallerAADObjectId" if you have a list of the Entra Object IDs if the users are not synced before).

Why I added a try/catch inside my code? If for example the user has not a license, it will throw an exception, so better to handle this scenario as well.

Hope it helps!