SharePoint and the infamous People Picker

So, I had a pretty interesting support case the other day. Cx stated that people picker wasn’t returning any results for users in a trusted domain. I was thinking ok I mean how hard could this be……. well so, my brain hurts now . So hopefully this will save someone else a huge headache.

Ok so let me paint the scenario for you, but first what is the issue?

Problem:

When you perform a people picker search for a user account that resides in a trusted domain no results are returned and SharePoint reports “User SPPrincipalInfo doesn’t seem to have a user principal name value” for our user we queried for

Scenario:

  • We have two domains with a two-way external trust configured. So, everyone trusts everyone here it’s a happy family.
    • Contoso.com
    • Fabrikam.com
  • SharePoint Servers, SharePoint Farm Service Accounts, and User accounts reside in the Contoso.com domain
  • For purposes of this article only user accounts reside in the Fabrikam.com domain
  • We are using a SAML provider that is using an out of the box AD claim provider. This is created by SharePoint when we create our trusted identity token issuer using the “-UseDefaultConfiguration” parameter like so:

    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(“C:\temp\ADFSSigningCert.cer”)

    New-SPTrustedRootAuthority -Name “Token Signing Cert” -Certificate $cert

    $ap = New-SPTrustedIdentityTokenIssuer -Name “ADFS SAML Provider” -Description “SAML Authentication for SharePoint” -realm “urn:sharepoint:www.contoso.com” -ImportTrustCertificate $cert -SignInUrl “https://adfs.Contoso.com/adfs/ls-UseDefaultConfiguration -IdentifierClaimIs USER-PRINCIPAL-NAME;

    $ap = Get-SPTrustedIdentityTokenIssuer -Identity “ADFS SAML Provider”;

    $ap.ProviderRealms.Add(“https://www.contoso.com”, “urn:sharepoint:www.contoso.com”);

    $ap.ProviderSignOutUri = “https://adfs.contoso.com/adfs/ls“;

    $ap.ClaimProviderName = “ADFS SAML Provider”;

    $ap.UseWReplyParameter = $true;

    $ap.Update();

Note:

The -UseDefaultConfiguration parameter will create a claim provider for you. Since this is our code (Microsoft) we are able to sink our hooks into AD to perform the user lookup based on the information entered into the people picker. This allows us to return a friendly display name instead of the awful identifying claim, which is some cases could be an employee ID

  • We have not disabled the out of the box AD Claim provider, but we have hidden the provider like so:
    • Do not disabled this claim provider it will break a lot of things that required windows authentication

      $cpm = Get-SPClaimProviderManager

      $ad = get-spclaimprovider -identity “AD”

      $ad.IsVisible = $false

      $cpm.Update()

  • We then must configure the people picker to search both domains like so:
    • If your domain short name differs from the first part of your domains FQDN you will need to disable netbios DC resolve.
    • For example, your FQDN for your domain is “Contoso.com” the Pre-Windows 2000 value for your domain is “Corp” and you log in as “Corp\user”.
      • Since “Corp” does not match “Contoso” we must run this PS and set the proper value for the attribute “ShortDomainName”

        Add-PSSnapin Microsoft.SharePoint.PowerShell -ea silentlycontinue

        $farm = get-spfarm

        $farm.Properties

        $farm.Properties[“disable-netbios-dc-resolve”] = $true

        $farm.Properties

        $farm.Update()

      • Otherwise we only have the need to run this PowerShell

        $wa = Get-SPWebApplication https://www.contoso.com

        $adsearchobj1 = New-Object Microsoft.SharePoint.Administration.SPPeoplePickerSearchActiveDirectoryDomain

        $adsearchobj1.DomainName = “Contoso.com”

        $adsearchobj1.ShortDomainName = “Contoso” #Optional

        $adsearchobj1.IsForest = $true #$true for Forest, $false for Domain

        $wa.PeoplePickerSettings.SearchActiveDirectoryDomains.Add($adsearchobj1)

        $wa = Get-SPWebApplication https://www.contoso.com

        $adsearchobj2 = New-Object Microsoft.SharePoint.Administration.SPPeoplePickerSearchActiveDirectoryDomain

        $adsearchobj2.DomainName = “Fabrikam.com”

        $adsearchobj2.ShortDomainName = “Fabrikam” #Optional

        $adsearchobj2.IsForest = $true #$true for Forest, $false for Domain

        $wa.PeoplePickerSettings.SearchActiveDirectoryDomains.Add($adsearchobj2)

        $wa.Update()

Note:

  • If you’re not certain if your short name differs from your FQDN here is some PowerShell to help you.
    • This will only return the local domain information though.

    Get the FQDN domain name

    (gwmi win32_computersystem).Domain

Get the Short name for the domain

(net config workstation) -match ‘Workstation domain\s+\S+$’ -replace ‘.+?(\S+)$’,’$1′

  • The -UseDefaultConfiguration parameter will create a claim provider for you. Since this is our code we are able to sink our hooks into AD to perform the user lookup based on the information entered into the people picker. This allows us to return a friendly display name instead of the awful identifying claim, which is some cases could be an employee ID.

Search with people picker for the user account in the fabrikam.com domain

  • Attempt to grant permissions to the user in the Fabrikam domain “PeoplePickerTest” using the people picker from here: https://www.contoso.com/_layouts/15/user.aspx
  • Within People Picker search for “peoplepickertest”
  • Search result:
    • No results found is the result

What You’ll see in the ULS logs and Network Monitor trace

Looking at netmon trace we see the first call

24167        12:47:08 PM 11/17/2016        13.6861171        w3wp.exe        10.19.141.20        RHWDC18.contoso.com         LDAPSASLBuffer        LDAPSASLBuffer:BufferLength: 667, AuthMechanism: GSS-SPNEGO        {LDAP:686, TCP:682, IPv4:676}

Here is our query to the Contoso.com domain

Filter: (|(&(objectCategory=person)(|(anr=peoplepickertest*)(SamAccountName=peoplepickertest*)(userPrincipalName=peoplepickertest*)))(&(objectCategory=group)(BIT_AND: (groupType)&2147483648)(|(anr=peoplepickertest*)(SamAccountName=peoplepickertest*))))

Here are the attributes we are requesting

Attributes: ( objectSID )( mail )( mobile )( displayName )( title )( department )( proxyAddresses )( cn )( samAccountName )( groupType )( userAccountControl )( distinguishedName )( objectClass )( userPrincipalName )( msexchmasteraccountsid )

24168        12:47:08 PM 11/17/2016        13.6869698        w3wp.exe        RHWDC18.contoso.com         10.19.141.20        LDAPSASLBuffer        LDAPSASLBuffer:BufferLength: 1027, AuthMechanism: GSS-SPNEGO        {LDAP:686, TCP:682, IPv4:676}

Here is our first search result

SearchResultEntry: CN=peoplepickertest@farbrikam.com,OU=Fabrikam,OU=Contacts from SimpleSync,OU=User,OU=Contoso,DC=contoso,DC=com

Here are the attributes returned

+ PartialAttribute: objectClass=( top )( person )( organizationalPerson )( contact )

+ PartialAttribute: cn=( peoplepickertest@Fabrikam.com )

+ PartialAttribute: title=( Business Application Consultant )

+ PartialAttribute: distinguishedName=( CN=peoplepickertest@Fabrikam.com,OU=Fabrikam,OU=Contacts from SimpleSync,OU=RHDC,OU=Contoso,DC=contoso,DC=com )

+ PartialAttribute: displayName=( Jones, Bob (Fabrikam) )

+ PartialAttribute: department=( AD_Medmanagement )

+ PartialAttribute: proxyAddresses=( x500:/o=Contoso.com/ou=First Administrative Group/cn=Recipients/cn=peoplepickertest.Farbrikam.com1 )( x500:/o=Contoso/ou=First Administrative Group/cn=Recipients/cn=peoplepickertest@Fabrikam.com1 )( X500:/o=Contoso/ou=First Administrative Group/cn=R

+ PartialAttribute: mail=( peoplepickertest@Farbrikam.com )

+ PartialAttribute: msExchMasterAccountSid=( )

In ULS we’ll see these entries for this search

SearchFromGC name = Contoso.com. start

GetAccountNameFromSid “0x0105000000000005150000007B005C46C7C60121369482B9798A0500” start

“0x0105000000000005150000007B005C46C7C60121369482B9798A0500” returned. returnValue=True

GetAccountNameFromSid “0x0105000000000005150000001741550EDE09584B0363450079C20000” start

GetAccountNameFromSid “0x0105000000000005150000001741550EDE09584B0363450079C20000” returned. returnValue=True

SearchFromGC name = Contoso.com. returned. Result count = 2

0x0105000000000005150000001741550EDE09584B0363450079C20000 = Userid:S-1-5-21-240468247-1264060894-4547331-49785

We have returned an entry with the SID S-1-5-21-240468247-1264060894-4547331-49785

This SID translates to fabrikam\peoplepickertest, but it was returned from the Contoso.com domain

We can see in the network monitor trace that this is returned as a contact with the attribute value msExchMasterAccountSid and no UPN

We then call out to the Fabrikam.com domain

39482        12:47:20 PM 11/17/2016        25.4775491        w3wp.exe        10.19.141.20        dc2vm.fabrikcam.com        LDAPSASLBuffer        LDAPSASLBuffer:BufferLength: 418, AuthMechanism: GSS-SPNEGO        {LDAP:1013, TCP:1012, IPv4:1009}

Here we search fabrikcam.com

SearchRequest: BaseDN: DC=Fabrikam,DC=com, SearchScope: WholeSubtree, SearchAlias: neverDerefAliases

We filter on SID

Filter: (&(objectSID=))

And we request these attributes

Attributes: ( objectSID )( mail )( mobile )( displayName )( title )( department )( proxyAddresses )( cn )( samAccountName )( groupType )( userAccountControl )( distinguishedName )( objectClass )( userPrincipalName )( msexchmasteraccountsid )

Here is the search result

39519        12:47:20 PM 11/17/2016        25.4835293        w3wp.exe        dc2vm.fabrikam.com        10.19.141.20        LDAPSASLBuffer        LDAPSASLBuffer:BufferLength: 789, AuthMechanism: GSS-SPNEGO        {LDAP:1013, TCP:1012, IPv4:1009}

SearchResultEntry: CN=PeoplePickerTest,OU=Contract,DC=Fabrikam,DC=Com

We return these attribute values

Frame: Number = 39519, Captured Frame Length = 847, MediaType = ETHERNET

– Attributes: 12 Partial Attributes

+ SequenceHeader:

+ PartialAttribute: objectClass=( top )( person )( organizationalPerson )( user )

+ PartialAttribute: cn=( PeoplePickerTest )

+ PartialAttribute: title=( Business Application Consultant )

+ PartialAttribute: distinguishedName=( CN=PeoplePickerTest,OU=Contract,DC=Fabrikam,DC=Com )

+ PartialAttribute: displayName=( PeoplePickerTest)

+ PartialAttribute: department=( AD_Medmanagement )

+ PartialAttribute: proxyAddresses=( smtp:PeoplePickerTest@fabrikam.com )( SMTP:PeoplePickerTest@fabrikam.com)

+ PartialAttribute: userAccountControl=( 512 )

+ PartialAttribute: objectSid=( )

+ PartialAttribute: sAMAccountName=( PeoplePickerTest )

+ PartialAttribute: userPrincipalName=( PeoplePickerTest@fabrikam.com )

+ PartialAttribute: mail=( PeoplePickerTest@fabrikam.com )

We then see these entries in the ULS for this search

SearchFromGC name = Fabrikam.com. start

GetAccountNameFromSid “0x0105000000000005150000001741550EDE09584B0363450079C20000” start

GetAccountNameFromSid “0x0105000000000005150000001741550EDE09584B0363450079C20000” returned. returnValue=True

SearchFromGC name = Fabrikam.com. returned. Result count = 1

We then see this error

User SPPrincipalInfo doesn’t seem to have a user principal name value. User: ‘fabrikam\PeoplePickerTest’, ID: ‘-1’

Conclusion:

  • We bind to the first result received with the SID
  • The attributes passed in the first search result was for a contact which does not have an object SID but does have a msEXCHMasterSID.
  • This attribute is included in the first search results, so we bind to this master SID and translate to a user account, which is the account we are expecting.
  • However, this search result does not contain a UserPrincipalName attribute, so we do not display the user.

Workarounds (in no particular order):

  • If you change the search order of the domains in the people picker searchadforests property to search the trusted domain Fabrikam.com first, then search Contoso.com. The people picker will bind to the first result it receives which will be the correct user object.

    Origninal configuration

    stsadm -o setproperty -url https://www.contoso.com -pn peoplepicker-searchadforests -pv “forest:contoso.com;forest:fabrikam.com”

    Reversed search order

    stsadm -o setproperty -url https://www.contoso.com -pn peoplepicker-searchadforests -pv “forest:fabrikam.com;forest:contoso.com

  • If you search for the user using domain\username that will return the correct results regardless of the people picker search order
    • This does not query every domain in the list
    • This is a direct LDAP query to the domain specified
  • If you copy the account name and paste it into the people picker search this will also return the correct user
    • This mimics the check name function for the account name
    • When you type in a user account we will actually start performing LDAP queries to return best bet results
      • For example, if I want to search for the account UserA as I type that into the people picker search the results may start displaying all the accounts with User in them and ultimately filter down to the exact result when you finish typing in the user account name
  • If you type in the users account name and then do a CTRL + K (check name function) this is the same as pasting in the users account name
    • This also uses the check name function

More of a permanent solution:

Ultimately the issue is people picker is resolving contact objects.  Since contacts can’t be used to log in with we can filter these out from the people picker results with a custom filter.

The filter is a LDAP search filter: (|(objectCategory=group)(&(objectCategory=person)(!objectClass=contact))).

We can test this filter using the custom search feature in Active Directory Users and Computers.

In SharePoint, we’ll fix this issue by using the below PowerShell:

==============================PowerShell Script==============================

Add-PSSnapin Microsoft.SharePoint.PowerShell -ea silentlycontinue

$wa = Get-SPWebApplication <Put Web App Here>

$wa.PeoplePickerSettings.SearchActiveDirectoryDomains | out-file c:\temp\pp_settings_before.txt

$wa.PeoplePickerSettings.SearchActiveDirectoryDomains.Clear()

$wa.update()

$wa.PeoplePickerSettings.SearchActiveDirectoryDomains

$wa = Get-SPWebApplication <Put Web App Here>

$adsearchobj1 = New-Object Microsoft.SharePoint.Administration.SPPeoplePickerSearchActiveDirectoryDomain

$adsearchobj1.DomainName = “Contoso.com”

$adsearchobj1.ShortDomainName = “Contoso”

$adsearchobj1.IsForest = $false

$newdomain.CustomFilter = “(|(objectCategory=group)(&(objectCategory=person)(!objectClass=contact)))”

$wa.PeoplePickerSettings.SearchActiveDirectoryDomains.Add($adsearchobj1)

$wa = Get-SPWebApplication <Put Web App Here>

$adsearchobj2 = New-Object Microsoft.SharePoint.Administration.SPPeoplePickerSearchActiveDirectoryDomain

$adsearchobj2.DomainName = “Fabrikam.com”

$adsearchobj2.ShortDomainName = “fabrikam”

$adsearchobj2.IsForest = $true

$newdomain2.CustomFilter = “(|(objectCategory=group)(&(objectCategory=person)(!objectClass=contact)))”

$wa.PeoplePickerSettings.SearchActiveDirectoryDomains.Add($adsearchobj2)

$wa.Update()

==============================PowerShell Script==============================

Additional information about People Picker and the MSExchMasterAccountSid property
https://blogs.iis.net/deanc/troubleshooting-people-picker-with-netmon

To see what is currently set in your environment use the following PowerShell
$webService = new-object Microsoft.SharePoint.Administration.SPWebService
$webService.PeoplePickerSearchReplicatedMasterSIDPropertyName

Now this is ignored if you copy paste in a value that exists in the local domain. This will return the local domains user object instead of the referenced object that is in the MSExchMasterAccountSid property