Advanced AAD Connect Permissions Configuration


I recently worked with a customer that needed assistance in configuring the additional permissions required for AAD Connect delegation.  After chasing down an incredible number of prerequisite information, I decided it would be more helpful to my customer to put together a tool that would help them configure the various permissions delegations.

AAD Connect supports several different write-back scenarios to the on-premises environment:

In addition, there are special permissions required to enable Password Hash Sync.

While many organizations have previously configured AAD Connect to use an account that is a member of Domain Admins or Enterprise Admins, it's not a best security practice.  However, for enabling password hash sync to the cloud or write data back to the on-premises environment, the AAD Connect service account needs to have the ability to extract the necessary information from the on-premises directory and update on-premises attributes.

Enter the tool.

With this tool, you can:

  • Grant the AAD Connect service account delegate permissions over objects in specific OUs for writeback
  • Initialize the environment for device writeback
  • Grant the AAD Connect service account  'Replicating Directory Changes' and 'Replicating Directory Changes All' permissions for password hash sync

Running the tool

After you've downloaded the tool, launch an elevated PowerShell prompt on your AAD Connect server.

The tool supports several parameters:

DeviceWriteBack
Use this parameter to configure Device Write Back. Using this parameter will require use of the AAD Connect modules, so AAD Connect must already be installed.  DeviceWriteBack also requires a particular version of the MSOnline cmdlets.  If you don't have them installed, it will attempt to go download them.

In order to make this work correctly across multiple platforms, I decided to use PowerShellGet to support the installation.  The latest Windows Azure AD v1 commandlets are available as a download that way, so it seemed to make sense to try to use it.  Also, I tried to find the URL from the download.connect.microsoft.com page, but we apparently now generate a token of some sort, rendering any download link invalid after a short period of time.  So, there's that, too.

This option will download and install the Microsoft Online Services Sign-In Assistant first (since it's a requirement), then proceed to install PowerShellGet and NuGet, and then finally, use PowerShellGeet to download and install the MSOnline Module.

If (!(Get-Module -ListAvailable MSOnline -ea silentlycontinue))
{
    Write-Host -ForegroundColor Yellow "This requires the Microsoft Online Services Module. Attempting to download and install."
    wget https://download.microsoft.com/download/5/0/1/5017D39B-8E29-48C8-91A8-8D0E4968E6D4/en/msoidcli_64.msi -OutFile $env:TEMP\msoidcli_64.msi
    If (!(Get-Command Install-Module))
    {
        wget https://download.microsoft.com/download/C/4/1/C41378D4-7F41-4BBE-9D0D-0E4F98585C61/PackageManagement_x64.msi -OutFile $env:TEMP\PackageManagement_x64.msi
    }
    msiexec /i $env:TEMP\msoidcli_64.msi /quiet /passive
    msiexec /i $env:TEMP\PackageManagement_x64.msi /qn
    Install-PackageProvider -Name Nuget -MinimumVersion 2.8.5.201 -Force -Confirm:$false
    Install-Module MSOnline -Confirm:$false -Force
    If (!(Get-Module -ListAvailable MSOnline))
    {
        Write-Host -ForegroundColor Red "This Configuration requires the MSOnline Module. Please download from https://connect.microsoft.com/site1164/Downloads/DownloadDetails.aspx?DownloadID=59185 and try again."
        Break
    }
}

Whew! That was painful.

Domain
Used to specify the NetBIOS domain name for the AAD Connect service account.  If this parameter is omitted, the current NetBIOS domain name is used.

I found I needed this parameter when I encountered a testing scenario where I had deployed AAD Connect on a DC--since I could validate the user using Get-ADUser, I had no need to look elsewhere.  Except for those other 99% of the use cases where AAD Connect is deployed on a member server.  If you don't specify the user account, this parameter is ignored.

ExchangeHybridWriteBack
Use this switch parameter to set the permissions for Exchange Hybrid WriteBack.  Using my good friend DSACLS, I trudge through whatever OUs have been specified in ExchangeHybridWriteBackOUs

ExchangeHybridWriteBackOUs
Use this parameter to specify target OUs to enable the service account writeback permissions. If this parameter is omitted, access is granted at the domain root.  The parameter is typed as an array, so you can enter one or more OUs in the standard array formats -- @('obj1','obj2') or 'obj1','obj2'.

OUs must be specified in the "OU=Child,OU=Parent,DC=domain,DC=com."  The format is validated using the following regular expression:

'^(ou=)[a-zA-Z\d\=\, ]*(,dc\=\w*,dc=\w*)'

That's the best that I could come up with--if you have a better one, I'd love to try it out.

OUs are also validated for whether or not the domain is valid.  The method that I use to do it is:

    Foreach ($OUPath in $OUs)
    {
        [array]$OUSplit = $OUPath.Split(",")
        foreach ($obj in $OUSplit)
        {
            If ($obj -like "DC=*")
            {
                $OUVer += $obj + ","
            }
        }
        $OUVer = $OUVer.TrimEnd(",").ToString()
        If (!(Test-Path "AD:\$OUVer" -ErrorAction SilentlyContinue))
        {
            $BadPaths += $OUVer
        }
        $OUVer = $null
    }

That way, I can verify if the DC=domain,DC=tld actually exists in your environment and then tell you otherwise.

I haven't put anything in to check if they're actually a valid OU structure yet.  It's not that I can't--just that I didn't put it in yet.  Gotta save something for the updates, right?

Forests
If you have more than one forest in your AAD Connect topology, you can use the Forests parameter to specify them for device writeback.  You must be logged on with an account that has enterprise admin privileges in the target forests.  Nuff said.

GroupWriteBack
Use this switch parameter to configure Office 365 Group writeback permissions.  It uses GroupWriteBackOU if the parameter is specified; otherwise, it defaults to the value in AD connector.  If no container is specified and Office 365 group writeback has not been configured in AAD Connect, the script will exit.

GroupWriteBackOU
Use this parameter to specify the Office 365 Groups writeback container.  If the container exists, the account specified will be granted access.  If the GroupWriteBackOU is specified and does not exist, it will be created (as long as the DN is formatted correctly and references a valid domain).

While the checking part was pretty easy, the process of creating a nested OU was not.  At least, not until I stumbled upon the [array]::Reverse method.  Using that, I was able to iteratively march through the array elements.

[array]$OuPath = $GroupWriteBackOU.Split(",")
            [array]::Reverse($OuPath)
            $OuDepthCount = 1
            foreach ($obj in $OuPath)
            {
                If ($OuDepthCount -eq 1)
                {
                    $Ou = $obj
                    # Do nothing else, since Test-Path will return a referral error when querying the very top level
                }
                Else
                {
                    Write-Host Current item is $obj
                    $Ou = $obj + "," + $Ou
                    If (!(Test-Path AD:\$Ou))
                    {
                        Write-Host -ForegroundColor Green "     Creating OU ($($Ou)) in path."
                        New-Item "AD:\$Ou" -ItemType OrganizationalUnit
                    }
                }
                $OuDepthCount++
            }

What's happening here:

  1. The value that's passed to $GroupWriteBackOU is in the form "OU=abc,OU=def,DC=domain,dc=com."  Using the Split method, I separate each of those elements into a new array element inside the array, so it ends up looking like this:
    OU=abc
    OU=def
    DC=domain
    DC=com
  2. Then, the magic of [array]::Reverse comes into play, and switches the order of the elements around, so that it now looks like this:
    DC=com
    DC=domain
    OU=def
    OU=abc

    No, I didn't lie. It's exactly reversed.

  3. In the next section, I start creating an DN path that I can validate using Test-Path AD:\$OU.  The sticky wicket is that if you try to do Test-Path "AD:\DC=com", you get this lovely error:
    test-path : A referral was returned from the server
     At line:1 char:1
     + test-path "AD:\DC=com"
     + ~~~~~~~~~~~~~~~~~~~~~~
     + CategoryInfo : ResourceUnavailable: (DC=com:String) [Test-Path], ADReferralException
     + FullyQualifiedErrorId : ADProvider:ItemExists::ADError,Microsoft.PowerShell.Commands.TestPathCommand

    Helpful, right? So, I put a counter in there for each iteration of the array, and if it's the first pass, we skip the Test-Path command.

  4. Finally, if the path tests as $False, then I create a new OU and continue the iteration until the end of the array.

I discovered some *really* interesting things about checking for the presence of certain paths.  For example, Get-ADObject and Get-ADOrganizationalUnit return hard errors when the target object isn't found.  You can't hide them inside a variable ($obj=Get-ADOrganizationalUnit $OU).  It ignores both -ErrorAction SilentlyContinue AND piping to Out-Null, which makes for an ugly script.

Test-Path AD:\$Ou was the answer to this problem.  However, if you just walk through the tree attempting to query for DC=com, you get the referral error I mentioned previously.  So you have to skip that one.  Can't ever be easy.

I'll move the OU checking code into the ExchangeHybridWriteBackOUs validation and return an error if the OU(s) specified don't exist.

Eventually.

PasswordHashSync
Use this parameter to set 'Replicating Directory Changes' and 'Replicating Directory Changes All' permissions.  This is actually the easiest bit of code to implement, since it's only two DSACLS commands for configuring the permissions:

If ($PasswordHashSync)
{
    $RootDSE = Get-ADRootDSE
    $DefaultNamingContext = $RootDSE.defaultNamingContext
    $ConfigurationNamingContext = $RootDSE.configurationNamingContext
    
    $cmd = "dsacls '$DefaultNamingContext' /G '`"$User`":CA;`"Replicating Directory Changes`";'`n"
    $cmd += "dsacls '$DefaultNamingContext' /G '`"$User`":CA;`"Replicating Directory Changes All`";'`n"
    Invoke-Expression $cmd | Out-Null
}

PasswordWriteBack
Use this parameter to enable password writeback.  It can read values from various places--the ExchangeHybridWriteBackOUs or PasswordWriteBackOUs parameters if specified.  The PasswordWriteBackOUs parameter takes precedence (so the ExchangeHybridWriteBackOUs parameter will be ignored).  Otherwise, it sets permissions at domain root.

PasswordWriteBackOUs
Use this parameter to specify target OUs to enable the service account writeback permissions. If this parameter is specified in conjunction with the parameter ExchangeHybridWriteBackOUs, this parameter will take effect.  If this parameter is omitted but the ExchangeHybridWriteBackOUs parameter is specified, PasswordWriteBack will use the ExchangeHybridWriteBackOUs values.  If neither parameter is supplied, then permissions will be delegated at the domain root.

TenantCredential
Use this parameter to specify the tenant credential used when returning domains from Office 365 for Device WriteBack.  If DeviceWriteBack is not selected, this parameter is not used.

TenantID
Use this parameter to specify the tenant GUID used when configuring Device WriteBack.  If the DeviceWriteBack parameter is not specified, this parameter is not used.

User
Specify the AAD account that will be granted permissions.  If no account is specified, attempt to locate the account through the connector properties.  If for some reason it can't figure out the account, it will exit (since you can't grant permissions to an account that doesn't exist). Duh.

Extracting the data from AAD Connect was actually a little more involved than I would have thought.  The user account information for the connector is not actually stored in the connector properties.  Why?  Ask the product group.

Instead, you can find the information located inside a backup of the AAD Connect configuration.  But where do you get that?  I'm so glad you asked.

You can use Get-ADSyncServerConfiguration to dump the configuration.  Then, you have to grab the connector ID for the on-premises AD connector, find the XML file related to that connector, and then look in following node:

[xml]$ConnectorXMLData = gc <xmlfile.xml>
$ConnectorXMLData.'ma-data'.'private-configuration'.'adma-configuration'.'forest-login-user'

If you don't specify the Domain parameter, the script will also examine the XML to find the NetBIOS domain of the user (stored in ma-data.private-configuration.adma-configuration.forest-login-domain).

VerifiedDomain
Specify a verfied domain for your AAD tenant instead of discovering a domain automatically.  This is only valid for configuring device writeback.  If you don't specify this parameter for configuring device writeback, the script will connect to AAD and return the first valid verified domain.  If the DeviceWriteBack parameter isn't specified, this parameter will be ignored.

I think that's all the bells and whistles for now.

If you didn't already see the link, it's on the TechNet Gallery at https://gallery.technet.microsoft.com/AD-Advanced-Permissions-49723f74.

Comments (4)

  1. turbomcp says:

    Thanks
    very helpful as always

Skip to main content