Configure cross-tenant synchronization using PowerShell or Microsoft Graph API
This article describes the key steps to configure cross-tenant synchronization using Microsoft Graph PowerShell or Microsoft Graph API. When configured, Microsoft Entra ID automatically provisions and de-provisions B2B users in your target tenant. For detailed steps using the Microsoft Entra admin center, see Configure cross-tenant synchronization.
Prerequisites
Source tenant
- Microsoft Entra ID P1 or P2 license. For more information, see License requirements.
- Security Administrator role to configure cross-tenant access settings.
- Hybrid Identity Administrator role to configure cross-tenant synchronization.
- Cloud Application Administrator or Application Administrator role to assign users to a configuration and to delete a configuration.
- Privileged Role Administrator role to consent to required permissions.
Target tenant
- Microsoft Entra ID P1 or P2 license. For more information, see License requirements.
- Security Administrator role to configure cross-tenant access settings.
- Privileged Role Administrator role to consent to required permissions.
Step 1: Sign in to the target tenant
Target tenant
Start PowerShell.
If necessary, install the Microsoft Graph PowerShell SDK.
Get the tenant ID of the source and target tenants and initialize variables.
$SourceTenantId = "<SourceTenantId>" $TargetTenantId = "<TargetTenantId>"
Use the Connect-MgGraph command to sign in to the target tenant and consent to the following required permissions.
Policy.Read.All
Policy.ReadWrite.CrossTenantAccess
Connect-MgGraph -TenantId $TargetTenantId -Scopes "Policy.Read.All","Policy.ReadWrite.CrossTenantAccess"
Step 2: Enable user synchronization in the target tenant
Target tenant
In the target tenant, use the New-MgPolicyCrossTenantAccessPolicyPartner command to create a new partner configuration in a cross-tenant access policy between the target tenant and the source tenant. Use the source tenant ID in the request.
If you get the error
New-MgPolicyCrossTenantAccessPolicyPartner_Create: Another object with the same value for property tenantId already exists
, you might already have an existing configuration. For more information, see Symptom - New-MgPolicyCrossTenantAccessPolicyPartner_Create error.$Params = @{ TenantId = $SourceTenantId } New-MgPolicyCrossTenantAccessPolicyPartner -BodyParameter $Params | Format-List
AutomaticUserConsentSettings : Microsoft.Graph.PowerShell.Models.MicrosoftGraphInboundOutboundPolicyConfiguration B2BCollaborationInbound : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyB2BSetting B2BCollaborationOutbound : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyB2BSetting B2BDirectConnectInbound : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyB2BSetting B2BDirectConnectOutbound : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyB2BSetting IdentitySynchronization : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantIdentitySyncPolicyPartner InboundTrust : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyInboundTrust IsServiceProvider : TenantId : <SourceTenantId> TenantRestrictions : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyTenantRestrictions AdditionalProperties : {[@odata.context, https://graph.microsoft.com/v1.0/$metadata#policies/crossTenantAccessPolicy/partners/$entity], [crossCloudMeetingConfiguration, System.Collections.Generic.Dictionary`2[System.String,System.Object]], [protectedContentSharing, System.Collections.Generic.Dictionary`2[System.String,System.Object]]}
Use the Invoke-MgGraphRequest command to enable user synchronization in the target tenant.
If you get an
Request_MultipleObjectsWithSameKeyValue
error, you might already have an existing policy. For more information, see Symptom - Request_MultipleObjectsWithSameKeyValue error.$Params = @{ userSyncInbound = @{ isSyncAllowed = $true } } Invoke-MgGraphRequest -Method PUT -Uri "https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/partners/$SourceTenantId/identitySynchronization" -Body $Params
Use the Get-MgPolicyCrossTenantAccessPolicyPartnerIdentitySynchronization command to verify
IsSyncAllowed
is set to True.(Get-MgPolicyCrossTenantAccessPolicyPartnerIdentitySynchronization -CrossTenantAccessPolicyConfigurationPartnerTenantId $SourceTenantId).UserSyncInbound
IsSyncAllowed ------------- True
Step 3: Automatically redeem invitations in the target tenant
Target tenant
In the target tenant, use the Update-MgPolicyCrossTenantAccessPolicyPartner command to automatically redeem invitations and suppress consent prompts for inbound access.
$AutomaticUserConsentSettings = @{ "InboundAllowed"="True" } Update-MgPolicyCrossTenantAccessPolicyPartner -CrossTenantAccessPolicyConfigurationPartnerTenantId $SourceTenantId -AutomaticUserConsentSettings $AutomaticUserConsentSettings
Step 4: Sign in to the source tenant
Source tenant
Start an instance of PowerShell.
Get the tenant ID of the source and target tenants and initialize variables.
$SourceTenantId = "<SourceTenantId>" $TargetTenantId = "<TargetTenantId>"
Use the Connect-MgGraph command to sign in to the source tenant and consent to the following required permissions.
Policy.Read.All
Policy.ReadWrite.CrossTenantAccess
Application.ReadWrite.All
Directory.ReadWrite.All
AuditLog.Read.All
Connect-MgGraph -TenantId $SourceTenantId -Scopes "Policy.Read.All","Policy.ReadWrite.CrossTenantAccess","Application.ReadWrite.All","Directory.ReadWrite.All","AuditLog.Read.All"
Step 5: Automatically redeem invitations in the source tenant
Source tenant
In the source tenant, use the New-MgPolicyCrossTenantAccessPolicyPartner command to create a new partner configuration in a cross-tenant access policy between the source tenant and the target tenant. Use the target tenant ID in the request.
If you get the error
New-MgPolicyCrossTenantAccessPolicyPartner_Create: Another object with the same value for property tenantId already exists
, you might already have an existing configuration. For more information, see Symptom - New-MgPolicyCrossTenantAccessPolicyPartner_Create error.$Params = @{ TenantId = $TargetTenantId } New-MgPolicyCrossTenantAccessPolicyPartner -BodyParameter $Params | Format-List
AutomaticUserConsentSettings : Microsoft.Graph.PowerShell.Models.MicrosoftGraphInboundOutboundPolicyConfiguration B2BCollaborationInbound : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyB2BSetting B2BCollaborationOutbound : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyB2BSetting B2BDirectConnectInbound : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyB2BSetting B2BDirectConnectOutbound : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyB2BSetting IdentitySynchronization : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantIdentitySyncPolicyPartner InboundTrust : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyInboundTrust IsServiceProvider : TenantId : <TargetTenantId> TenantRestrictions : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCrossTenantAccessPolicyTenantRestrictions AdditionalProperties : {[@odata.context, https://graph.microsoft.com/v1.0/$metadata#policies/crossTenantAccessPolicy/partners/$entity], [crossCloudMeetingConfiguration, System.Collections.Generic.Dictionary`2[System.String,System.Object]], [protectedContentSharing, System.Collections.Generic.Dictionary`2[System.String,System.Object]]}
Use the Update-MgPolicyCrossTenantAccessPolicyPartner command to automatically redeem invitations and suppress consent prompts for outbound access.
$AutomaticUserConsentSettings = @{ "OutboundAllowed"="True" } Update-MgPolicyCrossTenantAccessPolicyPartner -CrossTenantAccessPolicyConfigurationPartnerTenantId $TargetTenantId -AutomaticUserConsentSettings $AutomaticUserConsentSettings
Step 6: Create a configuration application in the source tenant
Source tenant
In the source tenant, use the Invoke-MgInstantiateApplicationTemplate command to add an instance of a configuration application from the Microsoft Entra application gallery into your tenant.
Invoke-MgInstantiateApplicationTemplate -ApplicationTemplateId "518e5f48-1fc8-4c48-9387-9fdf28b0dfe7" -DisplayName "Fabrikam"
Use the Get-MgServicePrincipal command to get the service principal ID and app role ID.
Get-MgServicePrincipal -Filter "DisplayName eq 'Fabrikam'" | Format-List
AccountEnabled : True AddIns : {} AlternativeNames : {} AppDescription : AppDisplayName : Fabrikam AppId : <AppId> AppManagementPolicies : AppOwnerOrganizationId : <AppOwnerOrganizationId> AppRoleAssignedTo : AppRoleAssignmentRequired : True AppRoleAssignments : AppRoles : {<AppRoleId>} ApplicationTemplateId : 518e5f48-1fc8-4c48-9387-9fdf28b0dfe7 ClaimsMappingPolicies : CreatedObjects : CustomSecurityAttributes : Microsoft.Graph.PowerShell.Models.MicrosoftGraphCustomSecurityAttributeValue DelegatedPermissionClassifications : DeletedDateTime : Description : DisabledByMicrosoftStatus : DisplayName : Fabrikam Endpoints : ErrorUrl : FederatedIdentityCredentials : HomeRealmDiscoveryPolicies : Homepage : https://account.activedirectory.windowsazure.com:444/applications/default.aspx?metadata=aad2aadsync|ISV9.1|primary|z Id : <ServicePrincipalId> Info : Microsoft.Graph.PowerShell.Models.MicrosoftGraphInformationalUrl KeyCredentials : {} LicenseDetails : ...
Initialize a variable for the service principal ID.
Be sure to use the service principal ID instead of the application ID.
$ServicePrincipalId = "<ServicePrincipalId>"
Initialize a variable for the app role ID.
$AppRoleId= "<AppRoleId>"
Step 7: Test the connection to the target tenant
Source tenant
In the source tenant, use the Invoke-MgGraphRequest command to test the connection to the target tenant and validate the credentials.
$Params = @{ "useSavedCredentials" = $false "templateId" = "Azure2Azure" "credentials" = @( @{ "key" = "CompanyId" "value" = $TargetTenantId } @{ "key" = "AuthenticationType" "value" = "SyncPolicy" } ) } Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$ServicePrincipalId/synchronization/jobs/validateCredentials" -Body $Params
Step 8: Create a provisioning job in the source tenant
Source tenant
In the source tenant, to enable provisioning, create a provisioning job.
Determine the synchronization template to use, such as
Azure2Azure
.A template has pre-configured synchronization settings.
In the source tenant, use the New-MgServicePrincipalSynchronizationJob command to create a provisioning job based on a template.
New-MgServicePrincipalSynchronizationJob -ServicePrincipalId $ServicePrincipalId -TemplateId "Azure2Azure" | Format-List
Id : <JobId> Schedule : Microsoft.Graph.PowerShell.Models.MicrosoftGraphSynchronizationSchedule Schema : Microsoft.Graph.PowerShell.Models.MicrosoftGraphSynchronizationSchema Status : Microsoft.Graph.PowerShell.Models.MicrosoftGraphSynchronizationStatus SynchronizationJobSettings : {AzureIngestionAttributeOptimization, LookaheadQueryEnabled} TemplateId : Azure2Azure AdditionalProperties : {[@odata.context, https://graph.microsoft.com/v1.0/$metadata#servicePrincipals('<ServicePrincipalId>')/synchro nization/jobs/$entity]}
Initialize a variable for the job ID.
$JobId = "<JobId>"
Step 9: Save your credentials
Source tenant
In the source tenant, use the Invoke-MgGraphRequest command to save your credentials.
$Params = @{ "value" = @( @{ "key" = "AuthenticationType" "value" = "SyncPolicy" } @{ "key" = "CompanyId" "value" = $TargetTenantId } ) } Invoke-MgGraphRequest -Method PUT -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$ServicePrincipalId/synchronization/secrets" -Body $Params
Step 10: Assign a user to the configuration
Source tenant
For cross-tenant synchronization to work, at least one internal user must be assigned to the configuration.
In the source tenant, use the New-MgServicePrincipalAppRoleAssignedTo command to assign an internal user to the configuration.
$Params = @{ PrincipalId = "<PrincipalId>" ResourceId = $ServicePrincipalId AppRoleId = $AppRoleId } New-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $ServicePrincipalId -BodyParameter $Params | Format-List
AppRoleId : <AppRoleId> CreatedDateTime : 7/31/2023 10:27:12 PM DeletedDateTime : Id : <Id> PrincipalDisplayName : User1 PrincipalId : <PrincipalId> PrincipalType : User ResourceDisplayName : Fabrikam ResourceId : <ServicePrincipalId> AdditionalProperties : {[@odata.context, https://graph.microsoft.com/v1.0/$metadata#appRoleAssignments/$entity]}
Step 11: Test provision on demand
Source tenant
Now that you have a configuration, you can test on-demand provisioning with one of your users.
In the source tenant, use the Get-MgServicePrincipalSynchronizationJobSchema command to get the schema rule ID.
$SynchronizationSchema = Get-MgServicePrincipalSynchronizationJobSchema -ServicePrincipalId $ServicePrincipalId -SynchronizationJobId $JobId $SynchronizationSchema.SynchronizationRules | Format-List
ContainerFilter : Microsoft.Graph.PowerShell.Models.MicrosoftGraphContainerFilter Editable : True GroupFilter : Microsoft.Graph.PowerShell.Models.MicrosoftGraphGroupFilter Id : <RuleId> Metadata : {defaultSourceObjectMappings, supportsProvisionOnDemand} Name : USER_INBOUND_USER ObjectMappings : {Provision Azure Active Directory Users, , , ...} Priority : 1 SourceDirectoryName : Azure Active Directory TargetDirectoryName : Azure Active Directory (target tenant) AdditionalProperties : {}
Initialize a variable for the rule ID.
$RuleId = "<RuleId>"
Use the New-MgServicePrincipalSynchronizationJobOnDemand command to provision a test user on demand.
$Params = @{ Parameters = @( @{ Subjects = @( @{ ObjectId = "<UserObjectId>" ObjectTypeName = "User" } ) RuleId = $RuleId } ) } New-MgServicePrincipalSynchronizationJobOnDemand -ServicePrincipalId $ServicePrincipalId -SynchronizationJobId $JobId -BodyParameter $Params | Format-List
Key : Microsoft.Identity.Health.CPP.Common.DataContracts.SyncFabric.StatusInfo Value : [{"provisioningSteps":[{"name":"EntryImport","type":"Import","status":"Success","description":"Retrieved User '[email protected]' from Azure Active Directory","timestamp":"2023-07-31T22:31:15.9116590Z","details":{"objectId": "<UserObjectId>","accountEnabled":"True","displayName":"User1","mailNickname":"user1","userPrincipalName":"use ... AdditionalProperties : {[@odata.context, https://graph.microsoft.com/v1.0/$metadata#microsoft.graph.stringKeyStringValuePair]}
Step 12: Start the provisioning job
Source tenant
Now that the provisioning job is configured, in the source tenant, use the Start-MgServicePrincipalSynchronizationJob command to start the provisioning job.
Start-MgServicePrincipalSynchronizationJob -ServicePrincipalId $ServicePrincipalId -SynchronizationJobId $JobId
Step 13: Monitor provisioning
Source tenant
Now that the provisioning job is running, in the source tenant, use the Get-MgServicePrincipalSynchronizationJob command to monitor the progress of the current provisioning cycle as well as statistics to date such as the number of users and groups that have been created in the target system.
Get-MgServicePrincipalSynchronizationJob -ServicePrincipalId $ServicePrincipalId -SynchronizationJobId $JobId | Format-List
Id : <JobId> Schedule : Microsoft.Graph.PowerShell.Models.MicrosoftGraphSynchronizationSchedule Schema : Microsoft.Graph.PowerShell.Models.MicrosoftGraphSynchronizationSchema Status : Microsoft.Graph.PowerShell.Models.MicrosoftGraphSynchronizationStatus SynchronizationJobSettings : {AzureIngestionAttributeOptimization, LookaheadQueryEnabled} TemplateId : Azure2Azure AdditionalProperties : {[@odata.context, https://graph.microsoft.com/v1.0/$metadata#servicePrincipals('<ServicePrincipalId>')/synchro nization/jobs/$entity]}
In addition to monitoring the status of the provisioning job, use the Get-MgAuditLogProvisioning command to retrieve the provisioning logs and get all the provisioning events that occur. For example, query for a particular user and determine if they were successfully provisioned.
Get-MgAuditLogDirectoryAudit | Select -First 10 | Format-List
ActivityDateTime : 7/31/2023 12:08:17 AM ActivityDisplayName : Export AdditionalDetails : {Details, ErrorCode, EventName, ipaddr...} Category : ProvisioningManagement CorrelationId : aaaa0000-bb11-2222-33cc-444444dddddd Id : Sync_aaaa0000-bb11-2222-33cc-444444dddddd_L5BFV_161778479 InitiatedBy : Microsoft.Graph.PowerShell.Models.MicrosoftGraphAuditActivityInitiator1 LoggedByService : Account Provisioning OperationType : Result : success ResultReason : User '[email protected]' was created in Azure Active Directory (target tenant) TargetResources : {<ServicePrincipalId>, } AdditionalProperties : {} ActivityDateTime : 7/31/2023 12:08:17 AM ActivityDisplayName : Export AdditionalDetails : {Details, ErrorCode, EventName, ipaddr...} Category : ProvisioningManagement CorrelationId : aaaa0000-bb11-2222-33cc-444444dddddd Id : Sync_aaaa0000-bb11-2222-33cc-444444dddddd_L5BFV_161778264 InitiatedBy : Microsoft.Graph.PowerShell.Models.MicrosoftGraphAuditActivityInitiator1 LoggedByService : Account Provisioning OperationType : Result : success ResultReason : User '[email protected]' was updated in Azure Active Directory (target tenant) TargetResources : {<ServicePrincipalId>, } AdditionalProperties : {} ActivityDateTime : 7/31/2023 12:08:14 AM ActivityDisplayName : Synchronization rule action AdditionalDetails : {Details, ErrorCode, EventName, ipaddr...} Category : ProvisioningManagement CorrelationId : aaaa0000-bb11-2222-33cc-444444dddddd Id : Sync_aaaa0000-bb11-2222-33cc-444444dddddd_L5BFV_161778395 InitiatedBy : Microsoft.Graph.PowerShell.Models.MicrosoftGraphAuditActivityInitiator1 LoggedByService : Account Provisioning OperationType : Result : success ResultReason : User '[email protected]' will be created in Azure Active Directory (target tenant) (User is active and assigned in Azure Active Directory, but no matching User was found in Azure Active Directory (target tenant)) TargetResources : {<ServicePrincipalId>, } AdditionalProperties : {}
Troubleshooting tips
Symptom - Insufficient privileges error
When you try to perform an action, you receive an error message similar to the following:
code: Authorization_RequestDenied
message: Insufficient privileges to complete the operation.
Cause
Either the signed-in user doesn't have sufficient privileges, or you need to consent to one of the required permissions.
Solution
Make sure you're assigned the required roles. See Prerequisites earlier in this article.
When you sign in with Connect-MgGraph, make sure you specify the required scopes. See Step 1: Sign in to the target tenant and Step 4: Sign in to the source tenant earlier in this article.
Symptom - New-MgPolicyCrossTenantAccessPolicyPartner_Create error
When you try to create a new partner configuration, you receive an error message similar to the following:
New-MgPolicyCrossTenantAccessPolicyPartner_Create: Another object with the same value for property tenantId already exists.
Cause
You are likely trying to create a configuration or object that already exists, possibly from a previous configuration.
Solution
Verify your syntax and that you are using the correct tenant ID.
Use the Get-MgPolicyCrossTenantAccessPolicyPartner command to list the existing object.
If you have an existing object, you might need to make an update using Update-MgPolicyCrossTenantAccessPolicyPartner
Symptom - Request_MultipleObjectsWithSameKeyValue error
When you try to enable user synchronization, you receive an error message similar to the following:
Invoke-MgGraphRequest: PUT https://graph.microsoft.com/v1.0/policies/crossTenantAccessPolicy/partners/<SourceTenantId>/identitySynchronization
HTTP/1.1 409 Conflict
...
{"error":{"code":"Request_MultipleObjectsWithSameKeyValue","message":"A conflicting object with one or more of the specified property values is present in the directory.","details":[{"code":"ConflictingObjects","message":"A conflicting object with one or more of the specified property values is present in the directory.", ... }}}
Cause
You are likely trying to create a policy that already exists, possibly from a previous configuration.
Solution
Verify your syntax and that you are using the correct tenant ID.
Use the Get-MgPolicyCrossTenantAccessPolicyPartnerIdentitySynchronization command to list the
IsSyncAllowed
setting.(Get-MgPolicyCrossTenantAccessPolicyPartnerIdentitySynchronization -CrossTenantAccessPolicyConfigurationPartnerTenantId $SourceTenantId).UserSyncInbound
If you have an existing policy, you might need to make an update using Set-MgPolicyCrossTenantAccessPolicyPartnerIdentitySynchronization command to enable user synchronization.
$Params = @{ userSyncInbound = @{ isSyncAllowed = $true } } Set-MgPolicyCrossTenantAccessPolicyPartnerIdentitySynchronization -CrossTenantAccessPolicyConfigurationPartnerTenantId $SourceTenantId -BodyParameter $Params