Controlling or scoping the synchronisation of #AzureAD DRS devices back to your on-premises #ActiveDirectory forest using #AADConnect

Azure Active Directory (Azure AD, AAD) Connect can optionally synchronise Azure AD device objects, registered either via Azure Device Registration Service (Azure DRS); InTune; or Office 365 Mobile Device Management (MDM), back to your on-premises Active Directory Domain Services (AD DS) forest for use by Active Directory Federation Services (AD FS).  (For a quick recap on DRS, see this post.)

One of the primary reasons for doing so is to introduce persistent single sign-on (PSSO), which inevitably helps reduce MFA interaction for users and generally provides a simpler, more enriched user authentication experience across devices and on-premises and cloud-based applications.

One of the issues that I’ve faced when implementing Azure DRS is the inability to control what can be registered.  Once it’s enabled, anyone can register anything as long as they undergo multi-factor authentication (MFA) – a configurable, yet wholly recommended setting.  In the case of my current customers they wanted more granularity.  Specifically, one request was this:

Write-back InTune managed devices and domain-joined, automatically registered devices.

In other words, do not simply write-back any and all bring your own device (BYOD) devices, which is the default.  The reason?  AD FS issues persistent single sign-on cookies to registered devices, which reduces the amount of credential collection and MFA – a positive and beneficial configuration if you trust the device.  Well, they don’t trust all devices, which is why they’re implementing InTune!

Identifying compliant and managed devices

The Azure device object has a few useful properties:

  • deviceTrustType (string)
  • isCompliant (Boolean)
  • isManaged (Boolean)

The trust type will be one of three: Workplace; ServerAd; or AzureAd; and is depicted in https://manage.windowsazure.com as TRUST TYPE.

deviceTrustType value Azure portal value Description
AzureAd Azure AD Joined Windows 10 Azure AD joined machine
ServerAd Domain Joined Windows 7 and Windows 8.1 domain-joined machines
Workplace Workplace Joined BYOD device that has registered either via DRS or InTune/O365MDM
Also used by Windows 10 domain-joined machines

The isCompliant and isManaged attributes describe the TRUST LEVEL column in the Azure portal.  isCompliant can be true, false or null; isManaged can be true or null (not seen a false yet), which mean the following:

isCompliant isManaged TRUST LEVEL
null true Managed
true true Compliant
false true Uncompliant
null null Authenticated

A trust level of managed describes an automatically workplace-joined/registered device, i.e. Windows 7 or Windows 8.1 domain-joined machines.

A trust level of compliant describes a device registered with and managed by InTune and currently in compliance with policy.

A trust level of authenticated describes a device that has been registered via DRS and the user registering the device has successfully proven themselves, which typically means they authenticated with MFA.

Windows 10 domain-joined machines are different to Windows 7 and Windows 8.1 devices.  Whereas Win7 and Win8.1 have a trust level of managed, Windows 10 has authenticated.  Windows 10 also does not have the isManaged Boolean set.

Therefore, to identify managed and compliant devices, the following matrix is used.

Trust Type isManaged/ isCompliant OS Device
ServerAd isManaged == true Windows 7/8.1
Workplace isManaged == NULL Windows 10 Windows 10 AD DS member
Workplace isCompliant == true BYOD – InTune
AzureAd isCompliant == true Windows 10 AAD join

Example device

Assuming you have at least one device registered in Azure AD, and AAD Connect is configured for device write-back, open up the Azure AD Connect synchronisation service manager and search the metaverse for device objects.  Randomly select one, click lineage and take a look at the AAD connector, for example:

aADConnect-ExampleDevice

Here we can see an iPhone that has enrolled with InTune.  The trust type is workplace and it is both compliant and managed.  As we will see in the next section, this device will be written back to the on-premises AD DS and the owner of this device will get Persistent Single Sign-On when accessing AD FS and Azure AD applications.

Filtering the devices

Given the information provided we have what we need to make a decision about whether or not to write-back the object:

  • deviceTrustType == ServerAd && isManaged == true (Trust type == Domain Joined; Trust Level == Managed)
    • Windows 7 or Windows 8.1 domain-joined machine (in-scope)
  • deviceTrustType == Workplace && isCompliant == true (Trust type == Workplace Joined; Trust Level == Compliant)
    • InTune-managed BYOD device (in-scope)
  • deviceTrustType == Workplace && isManaged == NULL & deviceOperatingSystem == Windows 10
    • Windows 10 domain-joined machine (in-scope)

So how best to achieve this in Azure AD Connect?  There are going to be multiple approaches.  I tried to align with documented recommendations and therefore chose to utilise the cloudFiltered attribute, even though I’m filtering on-premises objects and not filtering from the cloud.  I then implementing the following rules.  (I utilise the same naming convention but prefix custom rules with the customer name – for this post I have just written “Customer”.)

  • Customer In from AAD – Device Common.  This rule is used to contribute additional attributes not contributed by the out-of-box rule In from AAD – Device Common.  The new rule shares roughly the same configuration and has a higher precedence and different set of transformations, i.e. link type of join; no scoping filter; one join rule (deviceId == deviceId).  I implemented a precedence of 92 and two transformations:
    • A direct flow type; for target:deviceTrustType, source:deviceTrustType; with an update merge type
    • An expression flow type; for target:cloudFiltered, source:IIF statement; with an update merge type. The IIF statement is:
      • IIF([deviceTrustType] = “ServerAd” && [isManaged] <> True, True, IIF([deviceTrustType] = “Workplace” && IsNullOrEmpty([isManaged]) && [deviceOSVersion] = “Windows 10”, NULL, IIF([deviceTrustType] = “Workplace” && [isCompliant] <> True, True, NULL))))
  • Out to AD – Device Join SOAInAAD.  The out-of-box rule was copied and disabled.
  • Customer Out to AD – Devicze Join SOAInAAD.  This rule is a copy of  Out to AD – Device Join SOAInAAD, with two differences: higher precedence and additional scoping filters:
    • Group one: added cloudFiltered NOTEQUAL true
    • Group two: added cloudFiltered NOTEQUAL true

Applying the configuration

You apply configuration changes using PowerShell.  The following script implements the changes described in the preceding section – it disables Microsoft.OuttoADDeviceJoinSOAInAAD.nnn and creates Customer.OuttoADDeviceJoinSOAInAAD.001 and Customer.InfromAADDeviceCommon.001Customer.OuttoADDeviceJoinSOAInAAD.001 is a copy of the original OOB rule that is disabled that has a minor number of modifications.

Here’s the script.

CreateAzureDrsFilteringRules.ps1


## CreateAzureDrsFilteringRules.ps1
##   Example AAD Connect script to demonstrate how to apply a configuration that will allow
##   you to selectively write-back devices as opposed to accepting all registered devices

[String]$CustomRuleNamePrefix = 'Customer' # No spaces, e.g. "Contoso" or "AbstractSynapse"

[String]$CustomInfromAADDeviceCommonID = '9fe196c8-a80a-4f76-8a23-652d113aca29' # Go ahead and generate a GUID if you don't have one already, e.g. [Guid]::NewGuid().Guid|clip
[String]$CustomInfromAADDeviceCommonTag = '{0}.InfromAADDeviceCommon.001' -f $CustomRuleNamePrefix # Modify your version accordingly

[String]$CustomOutToADDeviceJoinSOAInAadID = '100f52da-c115-4f59-a348-872907e41490'
[String]$CustomOutToADDeviceJoinSOAInAadTag = '{0}.OuttoADDeviceJoinSOAInAAD.001' -f $CustomRuleNamePrefix # No changes to this rule

[String]$AzureADConnectorID = Get-ADSyncConnector | ? { $_.SubType -eq 'Windows Azure Active Directory (Microsoft)' } | select -ExpandProperty Identifier | Select -ExpandProperty Guid
[String]$AddsConnectorID = Get-ADSyncConnector | ? { $_.Type -eq 'AD' -and $_.GlobalParameters["Connector.DeviceContainerDn"] -ne $null} | select -ExpandProperty Identifier | select -ExpandProperty Guid

# Create custom "In from AAD Device Common rule"
Write-Host "Creating 'In from AAD Device Common' Sync Rule"
New-ADSyncRule  `
    -Name ('{0} In from AAD - Device Common' -f $CustomRuleNamePrefix) `
    -Identifier $CustomInfromAADDeviceCommonID `
    -Description 'Rule provides a filtering transformation to only provision certain registered devices.' `
    -Direction 'Inbound' `
    -Precedence 92 `
    -PrecedenceAfter '00000000-0000-0000-0000-000000000000' `
    -PrecedenceBefore '00000000-0000-0000-0000-000000000000' `
    -SourceObjectType 'device' `
    -TargetObjectType 'device' `
    -Connector $AzureADConnectorID `
    -LinkType 'Join' `
    -SoftDeleteExpiryInterval 0 `
    -ImmutableTag $CustomInfromAADDeviceCommonTag `
    -OutVariable AadDeviceCommonSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AadDeviceCommonSR[0] `
    -Source @('deviceTrustType') `
    -Destination 'deviceTrustType' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AadDeviceCommonSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AadDeviceCommonSR[0] `
    -Source @('deviceTrustType','isManaged','isCompliant') `
    -Destination 'cloudFiltered' `
    -FlowType 'Expression' `
    -ValueMergeType 'Update' `
    -Expression 'IIF([deviceTrustType] = "ServerAd" && [isManaged] <> True, True, IIF([deviceTrustType] = "Workplace" && IsNullOrEmpty([isManaged]) && [deviceOSVersion] = "Windows 10", NULL, IIF([deviceTrustType] = "Workplace" && [isCompliant] <> True, True, IIF([deviceTrustType] = "AzureAd" && [isCompliant] <> True, True, NULL))))' `
    -OutVariable AadDeviceCommonSR | Out-Null

New-Object  `
    -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.JoinCondition' `
    -ArgumentList 'deviceId', 'deviceId', $false `
    -OutVariable joinCondition | Out-Null

Add-ADSyncJoinConditionGroup  `
    -SynchronizationRule $AadDeviceCommonSR[0] `
    -JoinConditions @($joinCondition[0]) `
    -OutVariable AadDeviceCommonSR | Out-Null

Add-ADSyncRule -SynchronizationRule $AadDeviceCommonSR[0] | Out-Null

# Create custom "Out to AD - Device Join SOAInAAD" rule
Write-Host "Creating 'Out to AD Device Join SOAInAAD' Sync Rule"
New-ADSyncRule  `
    -Name ('{0} Out to AD - Device Join SOAInAAD' -f $CustomRuleNamePrefix) `
    -Identifier $CustomOutToADDeviceJoinSOAInAadID `
    -Description 'Supersedes Microsoft.OuttoADJoinSOAInAAD.001 - the only difference is the scoping filter, which descopes cloudFiltered devices' `
    -Direction 'Outbound' `
    -Precedence 94 `
    -PrecedenceAfter '00000000-0000-0000-0000-000000000000' `
    -PrecedenceBefore '00000000-0000-0000-0000-000000000000' `
    -SourceObjectType 'device' `
    -TargetObjectType 'msDS-Device' `
    -Connector $AddsConnectorID `
    -LinkType 'Provision' `
    -SoftDeleteExpiryInterval 0 `
    -ImmutableTag $CustomOutToADDeviceJoinSOAInAadTag `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('deviceId') `
    -Destination 'dn' `
    -FlowType 'Expression' `
    -ValueMergeType 'Update' `
    -Expression '"CN=" & StringFromGuid([deviceId]) & "," & %Microsoft.DeviceWriteBack.Container%' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('deviceId') `
    -Destination 'cn' `
    -FlowType 'Expression' `
    -ValueMergeType 'Update' `
    -Expression 'StringFromGuid([deviceId])' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('cloudAnchor') `
    -Destination 'msDS-CloudAnchor' `
    -FlowType 'Expression' `
    -ValueMergeType 'Update' `
    -Expression 'IIF(IsNullOrEmpty([cloudAnchor]),NULL,CGuid(Word([cloudAnchor],2,"_")))' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('displayName') `
    -Destination 'displayName' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('accountEnabled') `
    -Destination 'msDS-IsEnabled' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('alternativeSecurityId') `
    -Destination 'altSecurityIdentities' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('deviceId') `
    -Destination 'msDS-DeviceID' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('deviceObjectVersion') `
    -Destination 'msDS-DeviceObjectVersion' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('deviceOSType') `
    -Destination 'msDS-DeviceOSType' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('deviceOSVersion') `
    -Destination 'msDS-DeviceOSVersion' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('devicePhysicalIds') `
    -Destination 'msDS-DevicePhysicalIDs' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('registeredOwnerReference') `
    -Destination 'msDS-RegisteredOwner' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('registeredUsersReference') `
    -Destination 'msDS-RegisteredUsers' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncAttributeFlowMapping  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -Source @('isManaged') `
    -Destination 'msDS-IsManaged' `
    -FlowType 'Direct' `
    -ValueMergeType 'Update' `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

# This rule exists to facilitate the filtering of devices - cloudFiltered==true objects are excluded from scope
# and thus not provisioned
New-Object  `
    -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition' `
    -ArgumentList 'cloudCreated','true','EQUAL' `
    -OutVariable scopeCondition1 | Out-Null

New-Object  `
    -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition' `
    -ArgumentList 'cloudFiltered','true','NOTEQUAL' `
    -OutVariable scopeCondition2 | Out-Null

Add-ADSyncScopeConditionGroup  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -ScopeConditions @($scopeCondition1[0], $scopeCondition1[0]) `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

New-Object  `
    -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition' `
    -ArgumentList 'cloudCreated','false','EQUAL' `
    -OutVariable scopeCondition1 | Out-Null

New-Object  `
    -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition' `
    -ArgumentList 'cloudAnchor','','ISNOTNULL' `
    -OutVariable scopeCondition2 | Out-Null

New-Object  `
    -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition' `
    -ArgumentList 'alternativeSecurityId','','ISNOTNULL' `
    -OutVariable scopeCondition3 | Out-Null

New-Object  `
    -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.ScopeCondition' `
    -ArgumentList 'cloudFiltered','true','NOTEQUAL' `
    -OutVariable scopeCondition4 | Out-Null

Add-ADSyncScopeConditionGroup  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -ScopeConditions @($scopeCondition1[0], $scopeCondition2[0], $scopeCondition3[0], $scopeCondition4[0]) `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

New-Object  `
    -TypeName 'Microsoft.IdentityManagement.PowerShell.ObjectModel.JoinCondition' `
    -ArgumentList 'msDS-DeviceID','deviceId',$false `
    -OutVariable joinCondition1 | Out-Null

Add-ADSyncJoinConditionGroup  `
    -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] `
    -JoinConditions @($joinCondition1[0]) `
    -OutVariable AdDeviceJoinSoaInAadSR | Out-Null

Add-ADSyncRule -SynchronizationRule $AdDeviceJoinSoaInAadSR[0] | Out-Null

# Disable Microsoft.OuttoADJoinSOAInAAD.001 - as it's superseded by the custom rule
Write-Host "Disabling OOB sync rule Microsoft.OuttoADJoinSOAInAAD.001"
$MicrosoftOuttoADJoinSOAInAAD = Get-ADSyncRule |? { $_.ImmutableTag -like 'Microsoft.OuttoADJoinSOAInAAD.*' }
$MicrosoftOuttoADJoinSOAInAAD.Disabled = $true
$MicrosoftOuttoADJoinSOAInAAD | Add-ADSyncRule | Out-Null

To apply, simply save and execute it on an AAD Connect server as a member of the ADSyncAdmins group.

Once the configuration has been successfully applied you’ll need to execute a full synchronisation for all connectors.

The end result is that you will have all devices registered in Azure DRS in the metaverse, and a subset of these devices written back to the CN=Registered Devices container in your on-premises AD DS forest.  You can look see what is going to be provisioned versus what will remain filtered in the metaverse by looking at the cloudFiltered attribute of device objects.  NULL means not filtered (and therefore will be provisioned) and TRUE means filtered.

Summary

Azure Device Registration Service and InTune enrolment result in device objects registered in your Azure AD tenant and, optionally, written back to your on-premises Active Directory Domain Services forest by Azure AD Connect Sync.  By default all registered devices are written back regardless of the state of that device.  Some customers will not want all devices written back and will only want compliant devices or compliant devices and automatically-registered (domain-joined) devices written back.  This post illustrates how to achieve this configuration by describing data available to understand differences in registered devices and a practical and working (and successfully deployed, I might add) approach to implementing the necessary filtering in Azure AD Connect.  The result being more control over what devices are written back to on-premises and light-up persistent SSO.  The example also handles the differences with Windows 10 AD DS domain-joined devices, which look different to Windows 7 and Windows 8.1 domain-joined devices.

The script

Just in case of formatting issues or whatever, the PS1 file can be downloaded from here:

I don’t output to the screen, so to check that it’s worked, look for Sync Rules with an immutable tag starting with Customer or whatever you changed $CustomRuleNamePrefix to be and also ensure that the OOB SR with an immutable tag of Microsoft.OuttoADJoinSOAInAAD.* is disabled.

 

About Paul Williams

IT consultant working for Microsoft specialising in Identity Management and Directory Services.
This entry was posted in AADSync, Azure AD Connect and tagged , , , , , , , . Bookmark the permalink.

Leave a comment