This article describes setting up and maintaining the Entra ID profile synchronisation script (version 2.5 and later): the configuration sections, field mapping, group membership, and what you see in the output and logs.
The documentation consists of two parts:
- Part 1: the one-off setup - app registration, Azure Automation account, runbook, testing and scheduling.
- Part 2 (this article): setting up and maintaining the script.
$additionalFields, $customProfileFields, $teamMemberships and $roleMemberships are configured differently. When upgrading, do not copy your existing configuration one-to-one; instead, convert these four options to the new syntax (see Field mapping and Group membership). You can copy all other settings. The connection details are now at the top of the script in the CONNECT section. In addition, the MSAL.PS module is no longer required: the script now authenticates directly via
Connect-MgGraph. This requires Microsoft.Graph.Authentication version 2.0 or later; update this module in the Automation account if necessary. MSAL.PS can then be removed.This article consists of the following sections:
- The configuration sections
- Field mapping
- Group membership and roles
- Output and logging
- Maintenance: replacing the client secret
- Troubleshooting
The configuration sections
The script is divided into three configuration sections:
- CONNECT - connection details and secrets. These values are never logged.
- BASIC - the options that every customer must configure at a minimum.
- ADVANCED - optional configuration such as group membership and custom fields.
SettingsUsed line (JSON) to the console and the log on every run. Secrets from the CONNECT section are never included. See Output and logging.CONNECT
| Variable | Description |
|---|---|
$azAppId$azTenantId$azSecret
|
The details of the Azure app registration. For $azSecret, you can also refer to an Automation variable: $azSecret = Get-AutomationVariable -Name "azureClientSecretValue"
|
$kcTenantId |
The tenant name (Keycloak realm). This value is provided by Embrace. |
$authClientSecret |
The secret for sending data. This value is provided by Embrace. |
BASIC
| Variable | Description |
|---|---|
$entraGroup |
The name of the Entra ID group whose members (recursively, so nested groups are supported) are synchronised to Embrace. If a user has the disabled status in Entra ID, the user is shown as a disabled user on Social |
$dryRun |
$true: the script only displays an example of the output and sends nothing. $false: the data is actually sent to Embrace. See Output and logging for exactly what a dry run does. |
$dryRunSampleSize |
The number of users per group retrieved and displayed during a dry run. Tip: increase the number of users if you want to test advanced settings such as access groups. |
$syncManager |
$true also synchronises the manager field. This requires one additional Graph call per user; only enable it if the field is used. |
$syncUPN |
$true also synchronises the UserPrincipalName. |
$disableNonProvidedUsers |
$true: Embrace users with an ExternalId that are not (or are no longer) in the supplied list are deactivated. $false: accounts that are not supplied must be disabled manually |
$additionalFields |
The field mapping from Entra ID to Embrace profile fields. Every customer must configure this. See Field mapping. |
ADVANCED
| Variable | Description |
|---|---|
$assignSocialGroups |
$true: users are made members of Social's Members group (type Member) or Guests group (type Guest) based on their Entra ID UserType. |
$makeAllUsersSocialMembers |
$true: all synchronised users become Members, regardless of their UserType. Only works if $assignSocialGroups = $true. |
$forcedGuestGroup |
Optional: the name of an Entra ID group whose members are always made Social Guests, regardless of the two options above. |
$syncExtensions |
$true enables synchronisation of extensionAttributes. This requires one additional Graph call per user. |
$customProfileFields |
Mapping to custom Embrace profile fields (prefix x-user-attribute-custom-profile- in Keycloak). Same syntax as $additionalFields. |
$teamMemberships |
Embrace team membership based on Entra ID group membership. See Group membership. |
$roleMemberships |
Embrace role membership based on Entra ID group membership. |
$customMappings |
Assign a team or role based on the value of an Entra ID property (for example, everyone with Department = Finance). |
Order for determining Member/Guest: $forcedGuestGroup takes precedence over $makeAllUsersSocialMembers, which takes precedence over the UserType from Entra ID.
Field mapping ($additionalFields and $customProfileFields)
The mapping is a hashtable: 'Embrace-field-name' = 'Entra ID-field-name'. Each line stands alone - there are no commas between the lines, so you can easily enable and disable lines with a # without breaking the configuration.
$additionalFields = @{
'job-title' = 'jobTitle'
'company-name' = 'companyName'
'department' = 'department'
'displayname' = 'displayName'
'hire-date' = 'employeeHireDate'
'street-address' = 'streetAddress'
'office' = 'officeLocation'
'city' = 'city'
'country' = 'country'
'postal-code' = 'postalCode'
'office-phone' = 'businessPhones'
'mobile-phone' = 'mobilePhone'
}To combine multiple Entra ID fields in one Embrace field, use a list as the value:
'department' = 'company', 'department'If you do not need any additional fields, set the variable to $null.
Required fields
These fields are fixed in the script and are required for a valid synchronisation. They do not need to (and must not) be included in the mapping:
| Embrace | Entra ID |
|---|---|
| ExternalId | Id |
| FirstName | GivenName |
| LastName | Surname |
| AccountEnabled | AccountEnabled |
User extensions (extensionAttributes)
Entra ID allows you to add custom properties to a user profile. To include an extensionAttribute in the mapping, set $syncExtensions = $true (under ADVANCED):
$additionalFields = @{
'profile-birth-date' = 'extension_f946aada8c064232b6753f91f2ca3bf4_BirthDate'
}The same applies to custom Embrace profile fields:
$customProfileFields = @{
'hobbies' = 'extension_f946aada8c064232b6753f91f2ca3bf4_Hobbies'
'skills' = 'extensionAttribute1'
}$syncExtensions is only required when you use an extensionAttribute in the mapping. If you want to populate a field with a fixed or calculated value, you can do so through the customFields function - $syncExtensions does not need to be enabled for this.An Entra ID administrator can provide the correct names of these properties. You can also retrieve the names yourself with the PowerShell snippet below (enter the user ID of a test user with extensions):
Import-Module Microsoft.Graph.Authentication
Import-Module Microsoft.Graph.Beta.Users
# General configuration for connecting to Microsoft Entra ID
$azureApplicationId = ''
$azureTenantId = ''
$azureClientSecretValue = ''
# userid of a test user with extensions:
$userId = ''
# connect to Graph (requires Microsoft.Graph.Authentication 2.0 or higher)
$clientSecretCredential = New-Object System.Management.Automation.PSCredential($azureApplicationId, (ConvertTo-SecureString -AsPlainText -Force $azureClientSecretValue))
Connect-MgGraph -TenantId $azureTenantId -ClientSecretCredential $clientSecretCredential -NoWelcome
$userProperties = Get-MgBetaUser -UserId $userId
Write-Host $userProperties.AdditionalPropertiesTransforming or populating fixed values: the customFields function
If a value must first be modified (for example, a date conversion or language code), or if you want to populate a field with a fixed value, use the customFields function in the script. This function is called for each user and does not require $syncExtensions. Examples:
# Convert date of birth from an extensionAttribute to ISO format:
if (![string]::IsNullOrEmpty($entraUser.ExtensionProperty['extension_..._BirthDate']))
{
$user.Attributes | Add-Member -type NoteProperty -name "profile-birth-date" -Value ([datetime]::ParseExact($entraUser.ExtensionProperty['extension_..._BirthDate'], 'dd-MM-yyyy', $null)).ToString("o") -Force
}
# Derive language from country:
$lang = "nl"
switch ($entraUser.Country) {
"GB" { $lang = "en" }
"DE" { $lang = "de" }
}
$user.Attributes | Add-Member -type NoteProperty -name "language" -Value $lang -Force
# Populate a field with a fixed value (no $syncExtensions or mapping is required for this):
$user.Attributes | Add-Member -type NoteProperty -name "profile-office" -Value 'Head office' -ForceGroup membership and roles
In the script, you can configure members of an Entra ID group to automatically become members of a group within Embrace. This can be an intranet group and/or a permissions group.
Both types of group refer to Access groups in Embrace. The intranet administrator (specific permissions are required for this) must create these Access groups and share the names with the Entra ID administrator for further processing in the script. See Users - Permissions and roles.
Configure team membership as a hashtable: 'Embrace-team' = 'Entra ID-group-name'. Use / for subgroup paths. To assign multiple Entra ID groups to the same team, use a list as the value.
Intranet groups
Through Access groups, the intranet administrator creates a mapping for synchronising Entra ID groups to the Social intranet groups:
- Make sure Entra ID groups are allowed to be synchronised (toggle under Management > Group settings)
- Create an Access group: Management > Users > Access groups
- Link the Access group: Synchronisation tab > search for the Social intranet group
$teamMemberships = @{
'Embrace Suite/[Embrace intranet group name]' = '[Entra ID group name]'
}Permissions groups
For assigning roles, the best practice is to create a permissions group and link a role to it:
- Create a role: Management > Users > Roles
- Create a permissions group: Management > Users > Access groups
- Link the created role(s) to the access group
$teamMemberships = @{
'Embrace Suite/[Embrace permissions group name]' = '[Entra ID group name]'
}Combining multiple Entra ID groups into one Embrace group
If the members of two or more Entra ID groups must be placed in the same Embrace group, provide a list as the value:
$teamMemberships = @{
'Embrace Suite/[Embrace group name]' = '[Entra ID group 1]', '[Entra ID group 2]'
}The members of both Entra ID groups are combined and deduplicated: a user who belongs to both Entra ID groups becomes a member of the Embrace group only once. Conversely, the same Entra ID group can also be used for multiple Embrace groups.
Combining
Intranet groups and permissions groups can be combined in one configuration, and multiple Entra ID groups can refer to the same team:
$teamMemberships = @{
'Embrace Suite/News' = 'AD-Employees'
'Embrace Suite/Editors' = 'AD-Communications', 'AD-Marketing'
'Embrace Suite/Testers' = 'AD-TestGroup'
}Role membership
Role membership works in the same way; specify a client role as 'client-name/role-name':
$roleMemberships = @{
'Content Reader' = 'F_Finance_users'
'broker/read-token' = 'F_Sales_users'
}Custom mappings
If you want to assign a team or role based on an Entra ID property rather than group membership, use $customMappings:
$customMappings = @(
[PropertyMapping]::new([Action]::Assign, [EmbraceType]::Group, 'Embrace Suite/Testers', 'jobTitle', 'Tester', $false)
[PropertyMapping]::new([Action]::Assign, [EmbraceType]::Group, 'Embrace Suite/Consultants', 'Department', 'Consultancy', $false)
)$false) indicates whether the value should be treated as a regular expression. With $true, you can, for example, match part of an email domain.Output and logging
What exactly does a dry run do?
- Nothing is sent to Embrace.
- Only a sample of
$dryRunSampleSizeusers is retrieved per group. The displayed number of users found is therefore not the actual total. - The deactivation phase (
$disableNonProvidedUsers) is skipped completely during a dry run: because only a sample is retrieved, a preview could incorrectly show users outside the sample. - All messages include the
DRY RUNlabel.
SettingsUsed
At the start of every run, the script writes one line containing the complete active configuration (JSON), both to the console and to the log:
SettingsUsed: {"scriptVersion":"2.5","dryRun":true,"entraGroup":"...","additionalFields":[...],...}Connection details and secrets from the CONNECT section are never included here. Use this line to check that the configuration is as expected, and retain it when contacting support.
Summary at the end of the run
Summary: 143 users created or updated, 2 skipped (missing first name, last name or email), 5 users disabled, duration 00:04:12If a WARNING: ... sync calls failed message appears afterwards, look higher in the output for the FAILURE messages.
Maintenance: replacing the client secret
The client secret of the Azure app registration has a limited validity period (a maximum of 24 months). If the secret has expired, synchronisation fails immediately at startup with an Azure error message such as AADSTS7000222: The provided client secret keys ... are expired.
To replace the secret:
- In Azure (portal.azure.com), go to Microsoft Entra ID -> App registrations and open the app registration for the profile synchronisation.
- Go to Certificates & secrets and select New client secret. Enter a recognisable name, select an expiry period (for example, 24 months) and select Add.
- Copy the secret value immediately: it is displayed only once.
- Update the value where the script reads it:
- If you use an Automation variable (recommended): update the value of
azureClientSecretValueunder Shared Resources -> Variables in the Automation account. The script itself does not need to be changed. - If the secret is directly in the script: replace the value of
$azSecretin the CONNECT section of the runbook and publish the runbook again.
- If you use an Automation variable (recommended): update the value of
- Remove the expired secret from the app registration and check the synchronisation with a dry run (
$dryRun = $true) or during the next scheduled run.
The $authClientSecret (for sending data to Embrace) does not expire on a fixed date; it is provided by Embrace and only needs to be replaced if Embrace requests this.
Troubleshooting
-
"$additionalFields must be configured as a hashtable ..."
You are still using the configuration syntax from a script version older than 2.5. Convert the configuration to hashtable syntax; see Field mapping. -
"ME-ID Group '...' not found (configured in $teamMemberships)"
The specified Entra ID group does not exist or is spelt differently. The message states which configuration option uses the group. -
"WARNING: First name, Last name and Email cannot be empty. Skipping user ..."
The user is missing a required field in Entra ID and is skipped. Populate the field in Entra ID and run the synchronisation again. -
"AADSTS7000222: The provided client secret keys for app ... are expired"
The client secret of the Azure app registration has expired. See Maintenance: replacing the client secret. -
"The following required PowerShell modules are missing: ..." or "Microsoft.Graph.Authentication version ... 2.0 or higher is required"
See the troubleshooting section of part 1: the one-off setup.
Error messages concerning the Azure environment (modules, runtime, JWT) are described in part 1: the one-off setup.