How to programmatically configure cloud sync using MS Graph API
The following document describes how to replicate a synchronization profile from scratch using only MSGraph APIs.
The structure of how to replicate a synchronization profile consists of the following steps. They are:
Use these Microsoft Graph PowerShell commands to enable synchronization for a production tenant, a prerequisite for being able to call the Administration Web Service for that tenant.
Basic setup
Enable tenant flags
Connect-MgGraph -Scopes "DeviceManagementConfiguration.ReadWrite.All" ('-Environment <AzureEnvironment>')
$organizationId = (Get-MgOrganization).Id
$params = @{
onPremisesSyncEnabled = $true
}
Update-MgBetaOrganization -OrganizationId $organizationId -BodyParameter $params
This cmdlet enables synchronization for a tenant. It uses the Get-MgOrganization to get the organization's ID.
Create service principals
Next, we need to create the AD2AAD application/ service principal
You need to use this application ID 1a4721b3-e57f-4451-ae87-ef078703ec94. The displayName is the AD domain URL, if used in the portal (for example, contoso.com), but it may be named something else.
POST https://graph.microsoft.com/beta/applicationTemplates/1a4721b3-e57f-4451-ae87-ef078703ec94/instantiate
Content-type: application/json
{
displayName: [your app name here]
}
Create sync job
The output of the preceding command returns the objectId of the service principal that was created. For this example, the objectId is aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb. Use Microsoft Graph to add a synchronizationJob to that service principal.
Documentation for creating a sync job can be found here.
If you didn't record the ID, you can find the service principal by running the following MS Graph call. You need Directory.Read.All permissions to make that call:
GET https://graph.microsoft.com/beta/servicePrincipals
Then look for your app name in the output.
Run the following two commands to create two jobs: one for user/group provisioning, and one for password hash syncing. It's the same request twice but with different template IDs.
Call the following two requests:
POST https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/jobs
Content-type: application/json
{
"templateId":"AD2AADProvisioning"
}
POST https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/jobs
Content-type: application/json
{
"templateId":"AD2AADPasswordHash"
}
You need two calls if you want to create both.
Example return value (for provisioning):
HTTP 201/Created
{
"@odata.context": "https://graph.microsoft.com/beta/$metadata#servicePrincipals('aaaaaaaa-0000-1111-2222-bbbbbbbbbbbbc')/synchronization/jobs/$entity",
"id": "AD2AADProvisioning.fc96887f36da47508c935c28a0c0b6da",
"templateId": "ADDCInPassthrough",
"schedule": {
"expiration": null,
"interval": "PT40M",
"state": "Disabled"
},
"status": {
"countSuccessiveCompleteFailures": 0,
"escrowsPruned": false,
"code": "Paused",
"lastExecution": null,
"lastSuccessfulExecution": null,
"lastSuccessfulExecutionWithExports": null,
"quarantine": null,
"steadyStateFirstAchievedTime": "0001-01-01T00:00:00Z",
"steadyStateLastAchievedTime": "0001-01-01T00:00:00Z",
"troubleshootingUrl": null,
"progress": [],
"synchronizedEntryCountByType": []
}
}
Update targeted domain
For this tenant, the object identifier and application identifier of the service principal are as follows:
ObjectId: bbbbbbbb-1111-2222-3333-cccccccccccc
AppId: 00001111-aaaa-2222-bbbb-3333cccc4444
DisplayName: testApp
We're going to need to update the domain this configuration is targeting, so update the secrets for this domain.
Make sure the domain name you use is the same URL you set for your on-premises domain controller.
PUT – https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/secrets
Add the following key/value pair in the following value array based on what you're trying to do:
Enable both PHS and sync tenant flags { key: "AppKey", value: "{"appKeyScenario":"AD2AADPasswordHash"}" }
Enable only sync tenant flag (don't turn on PHS) { key: "AppKey", value: "{"appKeyScenario":"AD2AADProvisioning"}" }
Request body –
{
"value": [
{
"key": "Domain",
"value": "{\"domain\":\"ad2aadTest.com\"}"
}
]
}
The expected response is … HTTP 204/No content
Here, the highlighted "Domain" value is the name of the on-premises Active Directory domain from which entries are to be provisioned to Microsoft Entra ID.
Enable Sync password hashes on configuration blade
This section covers enabling syncing password hashes for a particular configuration. This situation is different than the AppKey secret that enables the tenant-level feature flag. This procedure is only for a single domain/config. You need to set the application key to the PHS one for this procedure to work end to end.
Grab the schema (warning, it's pretty large):
GET –https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/jobs/ [AD2AADProvisioningJobId]/schema
Take this CredentialData attribute mapping:
{ "defaultValue": null, "exportMissingReferences": false, "flowBehavior": "FlowWhenChanged", "flowType": "Always", "matchingPriority": 0, "targetAttributeName": "CredentialData", "source": { "expression": "[PasswordHash]", "name": "PasswordHash", "type": "Attribute", "parameters": [] }
Find the following object mappings with the following names in the schema
- Provision Active Directory Users
- Provision Active Directory inetOrgPersons
Object mappings are within the schema.synchronizationRules[0].objectMappings (For now you can assume there's only one Synchronization Rule)
Take the CredentialData Mapping from Step (2) and insert it into the object mappings in Step (3)
Your object mapping looks something like this:
{ "enabled": true, "flowTypes": "Add,Update,Delete", "name": "Provision Active Directory users", "sourceObjectName": "user", "targetObjectName": "User", "attributeMappings": [ ... }
Copy/paste the mapping from the Create AD2AADProvisioning and AD2AADPasswordHash jobs step into the attributeMappings array.
Order of elements in this array doesn't matter (the backend sorts for you). Be careful about adding this attribute mapping if the name already exists in the array. For example, if there's already an item in attributeMappings that has the targetAttributeName CredentialData, you may get conflict errors, or the preexisting and new mappings might be combined together. Usually, this isn't the desired outcome. Backend doesn't dedupe for you.
Remember to do this action for both Users and inetOrgpersons.
Save the schema that you create:
PUT – https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/jobs/ [AD2AADProvisioningJobId]/schema
Add the Schema in the request body.
Exchange hybrid writeback (Public Preview)
This section covers how to enable/disable and use Exchange hybrid writeback programmatically.
Enabling Exchange hybrid writeback programmatically requires two steps.
- Schema verification
- Create the Exchange hybrid writeback job
Schema verification
Before you enable and using Exchange hybrid writeback, cloud sync needs to determine if the on-premises Active Directory was extended to include the Exchange schema.
You can use the directoryDefinition:discover to initiate schema discovery.
POST https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/jobs/[AD2AADProvisioningJobId]/schema/directories/[ADDirectoryID]/discover
The expected response is … HTTP 200/OK
The response should look similar to the following output:
HTTP/1.1 200 OK
Content-type: application/json
{
"objects": [
{
"name": "user",
"attributes": [
{
"name": "mailNickName",
"type": "String"
},
...
]
},
...
]
}
Now check to see if the mailNickName attribute is present. If it is, then your schema is verified and contains the Exchange attributes. If not, review the prerequisites for Exchange hybrid writeback.
Create the Exchange hybrid writeback job
Once you have verified the schema you can create the job.
POST https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/jobs
Content-type: application/json
{
"templateId":"AAD2ADExchangeHybridWriteback"
}
Accidental deletes
This section covers how to programmatically enable/disable and use accidental deletes programmatically.
Enabling and setting the threshold
There are two per job settings that you can use, they are:
- DeleteThresholdEnabled - Enables accidental delete prevention for the job when set to 'true'. Set to 'true' by default.
- DeleteThresholdValue - Defines the maximum number of deletes that is allowed in each execution of the job when accidental deletes prevention is enabled. The value is set to 500 by default. So, if the value is set to 500, the maximum number of deletes allowed is 499 in each execution.
The delete threshold settings are a part of the SyncNotificationSettings
and can be modified via graph.
We're going to need to update the SyncNotificationSettings this configuration is targeting, so update the secrets.
PUT – https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/secrets
Add the following Key/value pair in the following value array based on what you're trying to do:
Request body -
{
"value":[
{
"key":"SyncNotificationSettings",
"value": "{\"Enabled\":true,\"Recipients\":\"foobar@xyz.com\",\"DeleteThresholdEnabled\":true,\"DeleteThresholdValue\":50}"
}
]
}
The "Enabled" setting in the example is for enabling/disabling notification emails when the job is quarantined.
Currently, PATCH requests for secrets aren't supported. So, you need to add all the values in the body of the PUT request, like in the example, to preserve the other values.
The existing values for all the secrets can be retrieved by using:
GET https://graph.microsoft.com/beta/servicePrincipals/{id}/synchronization/secrets
Allowing deletes
To allow these deletes to flow through after the job goes into quarantine, you need to issue a restart with just "ForceDeletes" as the scope.
Request:
POST https://graph.microsoft.com/beta/servicePrincipals/{id}/synchronization/jobs/{jobId}/restart
Request Body:
{
"criteria": {"resetScope": "ForceDeletes"}
}
Start sync job
The jobs can be retrieved again via the following command:
GET https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/jobs/
Documentation for retrieving jobs can be found here.
To start the jobs, issue this request, using the objectId of the service principal created in the first step, and the job identifiers returned from the request that created the job.
Documentation for how to start a job can be found here.
POST https://graph.microsoft.com/beta/servicePrincipals/8895955e-2e6c-4d79-8943-4d72ca36878f/synchronization/jobs/AD2AADProvisioning.fc96887f36da47508c935c28a0c0b6da/start
The expected response is … HTTP 204/No content.
Other commands for controlling the job are documented here.
To restart a job, use:
POST https://graph.microsoft.com/beta/servicePrincipals/8895955e-2e6c-4d79-8943-4d72ca36878f/synchronization/jobs/AD2AADProvisioning.fc96887f36da47508c935c28a0c0b6da/restart
{
"criteria": {
"resetScope": "Full"
}
}
Review status
Retrieve your job statuses via:
GET https://graph.microsoft.com/beta/servicePrincipals/[SERVICE_PRINCIPAL_ID]/synchronization/jobs/
Look under the 'status' section of the return object for relevant details