•
•
•
•
•
•

This week, I received an email from a colleague asking if there was a way to work around the default behavior described in https://docs.microsoft.com/en-us/azure/active-directory/connect/active-directory-aadconnectsync-implement-password-synchronization:

If a user is in the scope of password synchronization, the cloud account password is set to Never Expire.

# Solution Overview

Here’s a workaround that I’ve come up with to help in some scenarios.  It’s not perfect, by any means, and may seem a bit of a kludge, but it should help minimize exposure for accounts with expired passwords.  It requires 4 steps:

1. Synchronize the msDS-User-Account-Control-Computed computed attribute to a static AD attribute (I call it the UserAccountControlValue attribute) via scheduled task.  The attribute I selected by default is msDS-CloudExtensionAttribute1, but you can choose any unused attribute. You can even get fancy and create your own attribute.
2. Update the AD connector to synchronize the attribute you have selected (if it’s not already in the default attribute set).
3. Create an AAD Connect sync rule to check for the presence of the value 8388608 in the AD attribute specified for UserAccountControlValue attribute).  If it’s there, the set accountEnabled to FALSE and set the ‘info’ attribute to DisabledByExpiredPasswordSyncRule.
4. Create an AAD Connect sync rules to check the value of UserAccountControlValue attribute as well as the value of ‘info.’  If the UserAccountControlValue is set to something besides 8388608 (password expired) and the info attribute is set to DisabledByExpiredPasswordSyncRule, then set accountEnabled to TRUE and set info to NULL.  That way, we’re only enabling accounts that have previously been disabled by the sync rule (and not enabling other accounts that may have been purposely disabled).

Before you begin, disable the AAD Connect Sync Scheduler.

# Synchronize msDS-User-Account-Control-Computed

Back in the olden days, the userAccountControl attribute had a number of flags, and you could use those flags to determine the state of an account.  You can learn more about the property flags here (https://msdn.microsoft.com/en-us/library/ms680832(v=vs.85).aspx) and here (https://msdn.microsoft.com/en-us/library/aa772300(v=vs.85).aspx).  In the current releases of Windows, however, this attribute is actually computed real-time when the user properties are retrieved.  This is great to get the up-to-the-minute detail for account properties, but it’s not so great for state-based applications like FIM/MIM/AAD Connect since they import attributes into a database table and then perform their comparisons and operations on these static values.

To my understanding, this is fundamentally why we are unable to sync the “expired” state of an account.

Enter the workaround.

In this workaround, I created a script that retrieves all of these values for users on a regular basis, stores the value in memory, and then checks it against the previously saved value.  If they match, nothing happens.  If they’re different, the new value gets saved back out to the specified static attribute.  Messy?  Yes.  Kludgy? Definitely.  Works?  Three yes’s for me.  But, once that’s done, the AAD Connector delta import step can pick it up and then perform processing on it.

<#
.SYNOPSIS
Save the userAccountControl flag value to an extension attribute.

.PARAMETER UserAccountControlValue
Choose which attribute to store the computed userAccountControl value.  By
default, the script will use msDS-CloudExtensionAttribute1.

.PARAMETER Logfile
Specify the path to the log file. By default, the script will create and append
to a log file in $env:TEMP. #> Param ($UserAccountControlValue = 'msDS-CloudExtensionAttribute1',
$Logfile = "$env:TEMP\SyncPasswordExpiryLog.csv"
)

If (!(Test-Path $Logfile)) {$LogData = """" + "Identity" + """" + "," + """" + "Attribute" + """" + "," + """" + "OldValue" + """" + "," + """" + "NewValue" + """"
Add-Content -Path $Logfile -Value$LogData
}

function LogWrite($Identity,$Attribute, $OldValue,$NewValue)
{
$LogData = """" +$Identity + """" + "," + """" + $Attribute + """" + "," + """" +$OldValue + """" + "," + """" + $NewValue + """" Add-Content -Path$Logfile -Value $LogData } Write-Host "UserAccountControlValue attribute is$($UserAccountControlValue)"$cmd = "[array]$Users = Get-ADUser -Filter * -prop objectGuid, msDS-User-Account-Control-Computed,$($UserAccountControlValue)" Invoke-Expression$cmd

# Loop through all users
foreach ($obj in$users)
{
# Set Attribute values
$Existing =$obj.$($UserAccountControlValue)
$New =$obj.'msDS-User-Account-Control-Computed'
$User =$obj.DistinguishedName
If (!($Existing -eq$New))
{
LogWrite -Identity $User -Attribute$UserAccountControlValue -OldValue $Existing -NewValue$New
$UpdateUserCmd = "Set-ADUser "$($User)" -Replace @{ "$($UserAccountControlValue)" = "$($New)" }" Invoke-Expression$UpdateUserCmd
}
}

Run the script to copy the value from msDS-User-Account-Control-Computed to the attribute you’re going to use for $UserAccountControlValue. # Update the AD Connector to capture data from the$UserAccountControlValue attribute

If you use the default attribute for the script, msDS-CloudExtensionAttribute1, you’ll need to configure the AD connector to import that data from AD.  If you use one of the normal extension attributes (1-15), you won’t need to do this step, as those are selected by default.

1. Launch the Synchronization Service.
2. On the Connectors tab, select the AD connector for your forest, right-click, and select Properties.
3. Under Connector Designer, select Select Attributes.
4. Under Select Attributes, click the Show All checkbox.
5. Find the attribute you are using to store the UserAccountControl static value (msDS-CloudExtensionAttribute1 if you’re using the default script settings) and select it.
6. Click OK.
7. Run a Full Import on the AD Connector (if your environment is large, this will take a while).

# Create AAD Connect Sync Rule to disable expired accounts

The function of this rule is to perform two set operations on objects where msDS-cloudExtenstionAttribute1 (or whatever attribute you use to hold the UserAccountControl static data) is set to 8388608.  The rule will set accountEnabled to FALSE (which will prevent login to AAD) and the Info attribute to DisabledByExpiredPasswordSyncRule.  We’ll be using the value in that attribute in the next rule as well to determine which accounts can get re-enabled by a rule.

1. Launch the Synchronization Rules Editor.
2. Under Direction, select Inbound, and then click Add new rule.
3. Fill out the synchronization rule general information and click Next:
1. Name: In from AD – Disable Accounts in Cloud with Expired Passwords
2. Description: Disable Accounts in Cloud with Expired Passwords
4. Connected System Object Type: user
5. Metaverse Object Type: person
7. Precedence: [ choose a low, unused precedence, such as 80 ]
8. Tag: [ blank ]
9. Enable Password Sync: [ clear ]
10. Disabled: [clear]
4. On the Scoping Filter page, click Add Clause, and fill out the scoping details:
1. Under Attribute, select msDS-cloudExtensionAttribute1 (or whatever AD attribute you are storing the UserAccountControl static value in)
2. Under Operator, select EQUAL
3. In the Value box, type 8388608
5. Click Next.
6. On the Join rules page, click Next.
7. On the Transformations page, click Add Transformation, and add the following two transformations:
1. Flow Type: Constant; Target Attribute: accountEnabled; source: FALSE
2. Flow Type: Constant; Target Attribute: info; source DisabledByExpiredPasswordSyncRule
8. Click Save.

Optional: If you don’t disable the on-premises accounts that have expired passwords and want to scope this to only ‘active’ accounts, you can add an additional scoping filter for userAccountControl EQUALS 512.

# Create AAD Connect Sync Rule to re-enable account after password has been reset

After a user has updated the on-premises password and the UserAccountControl static value sync has been performed, you’ll probably want to re-enable users that had been disabled.  To do that, we’re going to create one more rule.  This rule will be scoped to objects that have both the “info” attribute set to DisabledByExpiredPasswordSyncRule (so we know we’re only enabling objects that the other sync rule disabled due to expired passwords) and that have the UserAccountControl static value attribute (msDS-CloudExtensionAttribute1, by default) set to a value other that 8388608.  If objects meet those two criteria, then we’ll set accountEnabled to TRUE and clear the info attribute.

1. Launch the Synchronization Rules Editor.
2. Under Direction, select Inbound, and then click Add new rule.
3. Fill out the synchronization rule general information and click Next:
2. Description: Re-Enable Accounts after Password Reset
4. Connected System Object Type: user
5. Metaverse Object Type: person
7. Precedence: [ choose a low, unused precedence, such as 79 ]
8. Tag: [ blank ]
9. Enable Password Sync: [ clear ]
10. Disabled: [clear]
4. On the Scoping Filter page, click Add Clause, and fill out the scoping details:
1. Under Attribute, select msDS-cloudExtensionAttribute1 (or whatever AD attribute you are storing the UserAccountControl static value in)
2. Under Operator, select NOTEQUAL
3. In the Value box, type 8388608
5. Click Add Clause to add another clause to the scoping filter, and fill out the scoping details:
1. Under Attribute, select info
2. Under Operator, select EQUAL
3. In the Value box, type DisabledByExpiredPasswordSyncRule
6. Click Next.
7. On the Join rules page, click Next.
8. On the Transformations page, click Add Transformation, and add the following two transformations:
1. Flow Type: Constant; Target Attribute: accountEnabled; source: TRUE
2. Flow Type: Constant; Target Attribute: info; source NULL
9. Click Save.

Optional: If you don’t disable the on-premises accounts that have expired passwords and want to scope this to only ‘active’ accounts, you can add an additional scoping filter for userAccountControl EQUALS 512.

# Process the users

Once you have the rules set, you can process the users.  I’d recommend tracking down a user with an expired password FIRST, and then just processing them:

1. Run the UserAccountControl sync script.
2. Verify that the user who should have an expired password has a value of 8388608 in the msDS-CloudExtensionAttribute1 attribute.
3. Run a Delta Import on the AD Connector.
4. On the AD Connector, right-click, select Search Connector Space, select RDN from the drop-down,  type CN=<username> and click Search.
5. Select the User from the results box, and then click Preview.
6. Ensure the Full Synchronization radio button is selected, then click Generate Preview.
8. Expand the node CN={GUID} and click on Export Attribute Flow.
9. Verify that accountEnabled has a Final Value of FALSE.

Repeat the preview steps for a user who does NOT have an expired password to verify that the accountEnabled flag is still set to TRUE.

If it all looks good, then you can select a few users, perform the manual synchronization (step 6 above), and export the values to Office 365.  If the testing works successfully, you should be able to re-enable the AAD Connect synchronization schedule.

Helping companies conquer inferior technology since 1997. I spend my time developing and implementing technology solutions so people can spend less time with technology. Specialties: Active Directory and Exchange consulting and deployment, Virtualization, Disaster Recovery, Office 365, datacenter migration/consolidation, cheese.

1. Joseph Ng says:

Hi Aaron,

Thx for your input and resolution on this mess from Azure AD.

Just a thought I believed you might have been considered so better ask you, comparing “forcing the user to change password for the next logon” with “disabling the user so he/she need to reset the account”, why you would prefer the latter one?

Many thx.
Best Regards,
Joseph

1. In this instance, the “force change password” option isn’t something we can set through AAD Connect. We can set it on-premises directly, and we can set it in-cloud directly, but the setting can’t be flowed. If we simply reset the password in AAD, there’s nothing to stop the user from resetting it and continuing to use that password.

2. Sam says:

To fully automate this, I assume the script would need to be scheduled to run every so often?

Thanks,

3. Chris Shriver says:

Hi there!

I’m at the step to Process the Users, however, when I try running the script provided, I receive the following error:
At line:1 char:1
+ Set-ADUser “CN=$ACCOUNTNAME$,OU=ServiceAccounts,DC=MY,DC=DOMAIN” -Replace @{ “msDS …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (CN=$,OU=Service…MY,DC=DOMAIN:ADUser) [Set-ADUser], ADIdentityNotFoundException + FullyQualifiedErrorId : ActiveDirectoryCmdlet:Microsoft.ActiveDirectory.Management.ADIdentityNot FoundException,Microsoft.ActiveDirectory.Management.Commands.SetADUser Do I need to make a specific modification to this script to allow it to work on all of our synced users? Thanks! 1. It looks like you have some sort of problem in the CN=$AccountName$value. In PowerShell, the$ is a reserved character, so if you have the actual character in use, you’ll need to escape it:

Set-ADUser “CN=$ACCOUNTNAME`$

4. Donald Young says:

First of all… Thank you! This is going to save me a lot of pain!

Do you know what the computed value would be for an Expired Account (vice password)? I also want to process these as well.

1. So, there’s no computed value for “expired.” An account expiration is stored in two values:
– accountExpires : nanoseconds since January 1, 1601
– AccountExpirationDate : A date we all understand

The useraccountcontrol is unchanged, as the account really isn’t “disabled.” So, there are a couple of options:

– Scheduled task to just disable expired accounts
— Search-ADAccount -Expired -UsersOnly | Set-ADUser -Enabled $false – Update script to write a ‘password expired’ value to the user object. — You can use SearchADAccount above or something like: — [datetime]$expirationdate = get-aduser -prop accountexpirationdate; if($expirationdate -and ($expirationdate -lt (Get0Date)) { Set-ADUser -blahblah }

5. Chris D says:

Where should I look for the Synchronization Rules Editor?
Thanks!

1. Click Start and type Synchronization Rules Editor and it will show up in the filtered programs list. 😉 Also, the default path is “C:\Program Files\Microsoft Azure AD Sync\UIShell\SyncRulesEditor.exe” so you can look for it there, too.

This site uses Akismet to reduce spam. Learn how your comment data is processed.