SharePoint: People Picker and Disabled Users

I’ve already written a few things on this topic, but I thought I’d add additional background, consolidate concepts, and highlight a new (to me) twist.

Background:

SharePoint Server (doesn’t matter which version) People Picker should not return disabled user accounts from Active Directory. If it does, there’s a configuration problem in either Active Directory or SharePoint.

The LDAP query that SharePoint comes up with, by default, looks like this: (search term: “josh”)

(|(&(objectCategory=person)(|(anr=josh*)(SamAccountName=josh*)))(&(objectCategory=group)(groupType:1.2.840.113556.1.4.803:=2147483648)(|(anr=josh*)(SamAccountName=josh*))))

If you’re savvy with LDAP syntax, you’ll see there’s nothing in the query to exclude disabled accounts. Instead, disabled accounts are returned in the LDAP query results, and then SharePoint code uses the value for the UserAccountControl attribute to decide if the user object is evaluated further or discarded. Remember that attribute name: UserAccountControl. That will be important later…

Now lets say that for whatever reason, like maybe one I discussed here, you want to use the old-school SharePoint 2010 People Picker behavior. You can enable that by setting the PeoplePickerSearchInMultipleForests property to “true” like this:

#Just set the URL for the web app
$webapp = Get-SPWebApplication "https://teams.contoso.com"
$ws = $webapp.WebService
$ws.PeoplePickerSearchInMultipleForests = $True
$ws.update()

When you do that, People Picker will use a completely different LDAP query. It will look something like this: (search term: “josh”)

“(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(name=josh*)(displayName=josh*)(cn=josh*)(mail=josh*)(sn=josh*)(SamAccountName=josh*)(proxyAddresses=SMTP:josh)(proxyAddresses=sip:josh)))”

Notice the “!(userAccountControl:1.2.840.113556.1.4.803:=2)” part. The userAccountControl part evaluates to disabled users. The ! means “not”. Put them together, and it’s saying exclude disabled users. So you see in this case, the LDAP query itself is excluding disabled users.

This is an important distinction, because if you take a network trace and look at the LDAP traffic, the default query (PeoplePickerSearchInMultipleForests = False) will show disabled users returned in the result set, whereas the old-school query (PeoplePickerSearchInMultipleForests = True) should not. I say “should” because there’s one scenario where it will. Read on…

The importance of UserAccountControl:

As I explained above, no matter which way you have PeoplePickerSearchInMultipleForests set, a users UserAccountControl value is used to determine whether or not the user is shown in People Picker results. If the value for UserAccountControl cannot be determined, then the user will be shown in People Picker results. I ran into this recently. This is the new twist I alluded to earlier:

Problem:

Disabled user accounts are showing up in People Picker.

Cause:

The disabled accounts had been moved to a “Terminated” OU in Active Directory. That OU had special permissions applied so that the SharePoint web application app pool account did not have permission to read the UserAccountControl attribute, but did have permission to read other attributes. Active Directory handles that scenario by returning values for the rest of the attributes (account name, email, first name, last name, etc), but returns no value for UserAccountControl. Since the user object was returned by the LDAP query, but enabled / disabled status could not be determined due to a missing value for UserAccountControl, the account is assumed to be enabled and shown in People Picker results.

Resolution:

Fix the permissions on the OU / user objects so the SharePoint application pool accounts can get a value for UserAccountControl.

Why the application pool accounts? — Because unless you have manually specified an account name and password within the People Picker settings, that is the account that is used to connect to Active Directory and execute the LDAP queries.

What if my AD people won’t do that? — Well, there’s one hacky workaround that I don’t necessarily recommend, but does work: You could set an explicit Deny on that OU for your SharePoint app pool account(s). In that case, the LDAP query will not be able to return any data about the users in that OU, so those users will not be shown in People Picker results regardless of their enabled / disabled status. The problem with this hack is just remembering that you’ve done it. It could sneak up on you years later, disguised as a different problem entirely.

Summary:

If People Picker is returning disabled accounts for Active Directory users, it’s likely for one of three reasons:

  1. You’re migrating user accounts with SID History, as I documented here: SharePoint: People Picker shows disabled user accounts in domain migration scenario
  2. You have set the PeoplePickerSearchReplicatedMasterSIDPropertyName property to “ObjectSID”, as I discussed at the bottom of this post: SharePoint: People Picker: PeoplePickerSearchInMultipleForests It’s also discussed here: SharePoint: Shared Mailboxes, disabled accounts, and People Picker
  3. The issue described above: You have messed with AD permissions so that the app pool account can read other attributes for the user, but not the UserAccountControl attribute.

Tools:

Here’s some PowerShell you can use to get information from AD about a given user. It attempts to determine enabled / disabled status based on the value of UserAccountControl. If it does not get a value for UserAccountControl, it throws a warning.

### Return AD properties for a user
### Run the PowerShell console as your app pool account for best accuracy.
### Just set the UserName variable.
$UserName = "User1"
$user = ([adsisearcher]"samAccountName=$($UserName)").FindOne()
IF($user)
{write-host "Account Name: " $user.properties.samaccountname 
write-host "DN: " $user.properties.distinguishedname
write-host "LastKnownParent: " $user.properties.lastknownparent
write-host "Display Name: " $user.properties.displayname 
write-host "Mail: "$user.properties.mail 
write-host "ProxyAddresses: " $user.properties.proxyaddresses
write-host "Created: " $user.properties.whencreated
write-host "Last Update: " $user.properties.whenchanged
Write-host "UserAccountControl: " $user.properties.useraccountcontrol
$objUser = New-Object System.Security.Principal.NTAccount($UserName)
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
write-host "SID: " $strSID.Value
IF($user.properties.useraccountcontrol)
{
$suser = [adsi]"LDAP://$($user.properties.item(""distinguishedname""))"
$uac=$suser.psbase.invokeget("useraccountcontrol")
    IF($uac -band 0x2)
    {write-host -foregroundcolor red "Account is DISABLED"}
    ELSE{write-host -foregroundcolor green "Account is ACTIVE / NOT disabled"} 
}
ELSE{write-host -foregroundcolor red "WARNING! AD did NOT return a value for UserAccountControl.  Unable to determine Enabled / Disabled status.  Check permissions on the OU and user object"}
} 
ELSE{write-host -foregroundcolor red "User $userName was NOT found in Active Directory. Likely deleted or in a remote Forest."} 

Here’s some PowerShell to spit out the current People Picker configuration for a given web application:

#Just set the URL for the web app
$wa = Get-SPWebApplication https://teams.contoso.com
$wa.PeoplePickerSettings
$wa.PeoplePickerSettings.SearchActiveDirectoryDomains
$ws = $wa.WebService
Write-host "PeoplePickerSearchInMultipleForests:" $ws.PeoplePickerSearchInMultipleForests
Write-host "PeoplePickerSearchReplicatedMasterSIDPropertyName:" $ws.PeoplePickerSearchReplicatedMasterSIDPropertyName  

Add a Comment