Second Life of a Hungarian SharePoint Geek

October 1, 2015

Change in the User Resolution in SharePoint 2013 People Picker

Filed under: Active Directory, People Picker, PowerShell, SP 2013 — Tags: , , , — Peter Holpar @ 22:31

After a SharePoint 2010 to SharePoint 2013 migration our users complained, that in the multiple Active Directory domain environment they have (I wrote about it recently) the People Picker does not resolve the users the same way it did earlier. Only a subset of the users was resolved, users from a few domains were not included in the results at all.

The reason of this issue is a change in the GetTrustedDomains method of the Microsoft.SharePoint.Utilities.SPUserUtility class. Now (in SP 2013) it includes an extra condition, checking the value of  SPWebService.ContentService.PeoplePickerSearchInMultipleForests.

If you need the same behavior as in the SP 2010 version, you should set the value of  the PeoplePickerSearchInMultipleForests property to true.

You can achieve it using PowerShell:

$cs = [Microsoft.SharePoint.Administration.SPWebService]::ContentService
$cs.PeoplePickerSearchInMultipleForests = $true
$cs.Update()

or via C#:

SPWebService.ContentService.PeoplePickerSearchInMultipleForests = true;
SPWebService.ContentService.Update();

Advertisements

September 29, 2015

People Picker is very slow when searching users

Filed under: Active Directory, People Picker, SP 2010 — Tags: , , — Peter Holpar @ 22:12

The environment of a customer of us consists of several Active Directory domains, a few of them were recently migrated from former domains.

Users of the SharePoint sites complained that when they try to look up users via the People Picker, the result is displayed only after a delay of  30-40 seconds, instead of the former 3-5 seconds.

I’ve tried to catch the problem using Wireshark, filtering for the LDAP protocol, as described in this post. However, I found no problem with the requests / responses, except for a delay of about 30 seconds, although no request using this protocol was sent in this time lag. Obviously, the sender process waited for a response sent using another protocol.

Removing the LDAP filter in Wireshark, I found these retransmission attempts:

No.     Time            Source                        Destination   Protocol Length  Info
3241    44.218621000    IP of the SharePoint Server   IP of the DC    TCP    66    53607 > msft-gc [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
3360    47.217136000    IP of the SharePoint Server   IP of the DC    TCP    66    [TCP Retransmission] 53607 > msft-gc [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
3791    53.221414000    IP of the SharePoint Server   IP of the DC    TCP    62    [TCP Retransmission] 53607 > msft-gc [SYN] Seq=0 Win=8192 Len=0 MSS=1460 SACK_PERM=1

The msft-gc is an LDAP-like protocol used to query the Global Catalog (GC) in the Active Directory (uses port 3268). The retransmission timeout (RTO) value of the packet 3360 was 3 sec., the RTO of the packet 3791 was 9 sec., both causing delay in the user search process.

The source IP was the address of the SharePoint server, the IP address in the destination is the address of a former Domain Controller (DC). The server, that acted as DC of a domain that was already migrated was online, but the DC-role was already demoted on it . The IP address of the server was registered in DNS, so the server could be PINGed, but it did not respond to LDAP requests (including msft-gc) anymore.

The entries in the ULS logs has provided further evidence, that there is an issue with the Global Catalog in the AD forest (see the SearchFromGC method in the stack trace below):.

08/06/2015 13:26:34.08     w3wp.exe (0x66BC)                           0x9670    SharePoint Foundation             General                           72e9    Medium      Error in resolving user ‘UserName‘ : System.Runtime.InteropServices.COMException (0x8007203A): The server is not operational.       at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)     at System.DirectoryServices.DirectoryEntry.Bind()     at System.DirectoryServices.DirectoryEntry.get_AdsObject()     at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)     at Microsoft.SharePoint.WebControls.PeopleEditor.SearchFromGC(SPActiveDirectoryDomain domain, String strFilter, String[] rgstrProp, Int32 nTimeout, Int32 nSizeLimit, SPUserCollection spUsers, ArrayList& rgResults)     at Microsoft.SharePoint.Utilities.SPUserUtility.ResolveAgainstAD(String input, Boolean inputIsEmailOnly, SPActiveDirectoryDomain globalCatalog, SPPrincipalType scopes, SPUserCo…    04482a74-c00f-4005-9cd3-11f765eca7a0
08/06/2015 13:26:34.08*    w3wp.exe (0x66BC)                           0x9670    SharePoint Foundation             General                           72e9    Medium      …llection usersContainer, TimeSpan searchTimeout, String customFilter)     at Microsoft.SharePoint.Utilities.SPActiveDirectoryPrincipalResolver.ResolvePrincipal(String input, Boolean inputIsEmailOnly, SPPrincipalType scopes, SPPrincipalSource sources, SPUserCollection usersContainer)     at Microsoft.SharePoint.Utilities.SPUtility.ResolvePrincipalInternal(SPWeb web, SPWebApplication webApp, Nullable`1 urlZone, String input, SPPrincipalType scopes, SPPrincipalSource sources, SPUserCollection usersContainer, Boolean inputIsEmailOnly, Boolean alwaysAddWindowsResolver).    04482a74-c00f-4005-9cd3-11f765eca7a0

Removing the orphaned DC entry from the AD  resolved the People Picker problem as well.

August 2, 2015

PowerShell Scripts around the SID

Filed under: Active Directory, Migration, PowerShell, SP 2010 — Tags: , , , — Peter Holpar @ 23:38

If you ever migrated SharePoint users you should be familiar either with the Move-SPUser cmdlet or its predecessor, the migrateuser stsadm operation:

$sourceURL = "http://mysites.company.com"
$web = Get-SPWeb $sourceURL
$user = $web.SiteUsers["domain\jdoe"]
Move-SPUser -Identity $user -NewAlias "newDomain\john.doe" –IgnoreSID

or

stsadm -o migrateuser –oldlogin "domain\jdoe" -newlogin "newDomain\john.doe" -ignoresidhistory

As you see, both method relies on the SID (or on its ignorance), but what is this SID and how can we read its value for our SharePoint or Active Directory users?

Each user in the Active Directory (AD) has a security identifier (SID) that is a unique, immutable identifier, allowing the user to be renamed without affecting its other properties.

Reading the SID of a SharePoint user from PowerShell is so simple as:

$web = Get-SPWeb http://YourSharePoint.com
$user = $web.AllUsers["domain\LoginName"]
$user.Sid

To be able to work with Active Directory from PowerShell, you need of course the Active Directory cmdlets. If your machine has no role in AD, you should install this PowerShell module using the steps described in this post.

Once you have this module installed, and you imported it via “Import-Module ActiveDirectory”, you can read the SID of a user in AD:

$user = Get-ADUser UserLoginNameWithoutDomain -Server YourDomainController.company.com
$user.SID.Value

Where UserLoginNameWithoutDomain is the login name of the user without the domain name, like jdoe in case of domain\jdoe, and YourDomainController.company.com is your DC responsible for the domain of your user.

If you need the SID history from AD as well, it’s a bit complicated. In this case I suggest you to read this writing as well.

$ADQuery = Get-ADObject –Server YourDomainController.company.com`
        -LDAPFilter "(samAccountName=UserLoginNameWithoutDomain )" `
        -Property objectClass, samAccountName, DisplayName, `
        objectSid, sIDHistory, distinguishedname, description, whenCreated |
        Select-Object * -ExpandProperty sIDHistory
$ADQuery | % { 
  Write-Host $_.samAccountName
  Write-Host Domain $_.AccountDomainSid.Value 
  Write-Host SID History
  $_.sIDHistory | % {
    $_.Value     
  }
  Write-Host ——————–
}

March 5, 2014

“The user does not exist or is not unique” Error When a User is Disabled in Active Directory

Filed under: Active Directory, Reflection, SP 2010 — Tags: , , — Peter Holpar @ 22:54

At one of our customers there is an Active Directory that contains several domains. There are overlapping user accounts in these domains, like DomainA\User1 and DomainB\User1, often one of these accounts is disabled, while the same account in the other domain is active.

Recently I worked with a list, where I chose a user in People picker without problems, however, when saving the list item, I received an error:

The user does not exist or is not unique.<nativehr>0x81020054</nativehr><nativestack></nativestack>

image

The full stack trace:

[COMException (0x81020054): The user does not exist or is not unique.<nativehr>0x81020054</nativehr><nativestack></nativestack>]
   Microsoft.SharePoint.Library.SPRequestInternalClass.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback) +0
   Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback) +406

[SPException: The user does not exist or is not unique.]
   Microsoft.SharePoint.SPGlobal.HandleComException(COMException comEx) +27609794
   Microsoft.SharePoint.Library.SPRequest.AddOrUpdateItem(String bstrUrl, String bstrListName, Boolean bAdd, Boolean bSystemUpdate, Boolean bPreserveItemVersion, Boolean bUpdateNoVersion, Int32& plID, String& pbstrGuid, Guid pbstrNewDocId, Boolean bHasNewDocId, String bstrVersion, Object& pvarAttachmentNames, Object& pvarAttachmentContents, Object& pvarProperties, Boolean bCheckOut, Boolean bCheckin, Boolean bMigration, Boolean bPublish, String bstrFileName, ISP2DSafeArrayWriter pListDataValidationCallback, ISP2DSafeArrayWriter pRestrictInsertCallback, ISP2DSafeArrayWriter pUniqueFieldCallback) +28003791
   Microsoft.SharePoint.SPListItem.AddOrUpdateItem(Boolean bAdd, Boolean bSystem, Boolean bPreserveItemVersion, Boolean bNoVersion, Boolean bMigration, Boolean bPublish, Boolean bCheckOut, Boolean bCheckin, Guid newGuidOnAdd, Int32& ulID, Object& objAttachmentNames, Object& objAttachmentContents, Boolean suppressAfterEvents, String filename) +26729773
   Microsoft.SharePoint.SPListItem.UpdateInternal(Boolean bSystem, Boolean bPreserveItemVersion, Guid newGuidOnAdd, Boolean bMigration, Boolean bPublish, Boolean bNoVersion, Boolean bCheckOut, Boolean bCheckin, Boolean suppressAfterEvents, String filename) +26726382
   Microsoft.SharePoint.SPListItem.Update() +161
   Microsoft.SharePoint.WebControls.SaveButton.SaveItem(SPContext itemContext, Boolean uploadMode, String checkInComment) +20288612
   Microsoft.SharePoint.WebControls.SaveButton.OnBubbleEvent(Object source, EventArgs e) +1315
   System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +70
   System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +29
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2981

A similar error can we reproduce when selecting a user in People picker, then before save, we set the account in AD as disabled:

image

When saving the item, the same “The user does not exist or is not unique” exception is thrown as long the user was not yet available in the user list of SharePoint. If the user was already former created in the SharePoint user list, there was no error, even if the user was disabled in AD.

However, in our case, it was a bit more complicated. When I searched the user in People picker, only one of the accounts was shown up, the one that was not disabled (in this case, from DomainA). The disabled account from the other domain was not included in the People picker. So far, so good.

image

However, after I selected the user and closed the People picker by pressing OK, the other, disabled user (from DomainB) was set in the field, and caused the issue at save.

image

When I set the account by explicitly specifying the domain (like DomainA\testuser) I hade no such problems.

After a user from DomainB was resolved by People picker, both accounts was shown up in the result list (at least, for a while).

I’ve created a few network traffic capture using Wireshark, and found that the LDAP queries again the AD were correct, searching only accounts that are not disabled (see condition (!(userAccountControl:1.2.840.113556.1.4.803:=2) below).

(&(&(&(objectCategory=person)(objectClass=user))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))(|(|(|(|(|(|(|(name=testuser*)(displayName=testuser*))(cn=testuser*))(mail=testuser*))(sn=testuser*))(SamAccountName=testuser*))

The response from AD was OK as well, including only the active user from DomainA.

Using SQL Server Profiler I found queries, like the ones below:

exec sp_executesql N’DECLARE @DocParentIdForRF uniqueidentifier SELECT TOP 1 @DocParentIdForRF = Docs.Id FROM Docs WHERE Docs.SiteId = @SITEID AND Docs.DirName = @FDN AND Docs.LeafName = @FLN;   SELECT TOP(@NUMROWS) t1.[Type] AS c0, UserData.[tp_ContentTypeId], UserData.[nvarchar10], UserData.[tp_ID], UserData.[tp_CopySource], UserData.[tp_Version], t1.[Id] AS c4, UserData.[nvarchar4], UserData.[nvarchar9], UserData.[nvarchar5], UserData.[tp_Created], t1.[SortBehavior] AS c1, UserData.[tp_HasCopyDestinations], UserData.[nvarchar1], UserData.[tp_ModerationStatus], UserData.[tp_Level], UserData.[nvarchar6], t1.[MetaInfo] AS c3, t1.[ScopeId] AS c5, UserData.[tp_Modified], CASE WHEN DATALENGTH(t1.DirName) = 0 THEN t1.LeafName WHEN DATALENGTH(t1.LeafName) = 0 THEN t1.DirName ELSE t1.DirName + N”/” + t1.LeafName END  AS c2, UserData.[nvarchar3] FROM UserData INNER MERGE JOIN Docs AS t1 WITH(NOLOCK) ON (UserData.[tp_RowOrdinal] = 0) AND (t1.SiteId=UserData.tp_SiteId) AND (t1.SiteId = @SITEID) AND (t1.ParentId = UserData.tp_ParentId) AND (t1.Id = UserData.tp_DocId) AND ( (UserData.tp_Level = 1) ) AND (t1.Level = UserData.tp_Level) AND (t1.IsCurrentVersion = 1) AND (t1.Level = 1 OR t1.Level =  2) WHERE ( (UserData.tp_Level = 1) ) AND (UserData.tp_SiteId=@SITEID AND (UserData.tp_ParentId=@DocParentIdForRF)) AND (UserData.tp_RowOrdinal=0) AND ((UserData.[bit3]=0 ) AND ((UserData.[tp_ContentTypeId] = @L2IMG) AND (((UserData.[nvarchar3] LIKE @L3TXP) OR (UserData.[nvarchar1] LIKE @L4TXP)) OR ((UserData.[nvarchar4] LIKE @L4TXP) OR (UserData.[nvarchar5] LIKE @L3TXP)))) AND t1.SiteId=@SITEID AND (t1.ParentId=@DocParentIdForRF)) ORDER BY UserData.[nvarchar3]  ASC ,UserData.[tp_ID]  ASC  OPTION (FORCE ORDER, MAXDOP 1)’,N’@LFFP uniqueidentifier,@SITEID uniqueidentifier,@L2IMG varbinary(8000),@L3TXP nvarchar(4000),@L4TXP nvarchar(4000),@FDN nvarchar(4000),@FLN nvarchar(4000),@NUMROWS bigint,@RequestGuid uniqueidentifier’,@LFFP=’00000000-0000-0000-0000-000000000000′,@SITEID=’94053632-22BC-4CEA-A146-8EC049097DA3′,@L2IMG=0x010A00980232D0CB4C3C41A835ED215B890946,@L3TXP=N’%DomainA\testuser%’,@L4TXP=N’DomainA\testuser%’,@FDN=N’my/_catalogs’,@FLN=N’users’,@NUMROWS=12,@RequestGuid=’5DBABDC5-3182-4866-8FBC-0E9244FE6733′

and

exec proc_SecResolvePrincipal @SiteId=’94053632-22BC-4CEA-A146-8EC049097DA3′,@Input=N’DomainA\testuser’,@InputIsEmailOnly=0,@AccountName=N’DomainA\testuser’,@DLAlias=NULL,@DLAliasServerAddress=NULL,@SearchScope=1,@RequestGuid=’5DBABDC5-3182-4866-8FBC-0E9244FE6733′

While the user did not yet exist in the SharePoint site, neither of these queries had a result.

Using Reflector I found that the following classes and methods have significant roles in the user login name resolution:

Microsoft.SharePoint.Utilities.SPUtility class, ResolvePrincipalInternal, ResolveWindowsPrincipal, SearchAgainstAD methods
Microsoft.SharePoint.WebControls.PeopleEditor class, GetAccountFromSid method
Microsoft.SharePoint.Utilities.SPActiveDirectoryPrincipalBySIDResolver class, ResolvePrincipal method
Microsoft.SharePoint.Utilities.SPUserUtility class, ResolveAgainstUserInfoList method
System.Security.Principal.NTAccount class, Translate and TranslateToSids methods

There are a few approach to resolve the user via the methods above:

SPPrincipalInfo pi = SPUtility.ResolveWindowsPrincipal(web.Site.WebApplication, "testuser", SPPrincipalType.User, false);

or

SecurityIdentifier sid = (SecurityIdentifier)new NTAccount("testuser").Translate(typeof(SecurityIdentifier));

or using Reflection through the internal static GetAccountFromSid method of the PeopleEditor class:

  1. byte[] binaryForm = null;
  2. string ntAccountName = null;
  3. string domainName = null;
  4. string userName = null;
  5.  
  6. string lookUpName = "testuser";
  7. Console.WriteLine("Account name to be resolved: {0}", lookUpName);
  8.  
  9. SecurityIdentifier sid = (SecurityIdentifier)new NTAccount(lookUpName).Translate(typeof(SecurityIdentifier));
  10. binaryForm = new byte[sid.BinaryLength];
  11. sid.GetBinaryForm(binaryForm, 0);
  12.  
  13. Console.WriteLine("{0} (Original SID)", sid);
  14.  
  15. // call internal static method PeopleEditor.GetAccountFromSid
  16. object sidTypeInvalid = null;
  17. // sidTypeInvalid = SPAdvApi32.SID_NAME_USE.SidTypeInvalid;
  18. // hack to get the Microsoft.SharePoint assembly
  19. Assembly spAssembly = typeof(SPWeb).Assembly;
  20.  
  21. // get the internal enum type from the internal class
  22. Type sidNameUseType = spAssembly.GetType("Microsoft.SharePoint.Win32.SPAdvApi32+SID_NAME_USE");
  23. FieldInfo fi_SidTypeInvalid = sidNameUseType.GetField("SidTypeInvalid", BindingFlags.Public | BindingFlags.Static);
  24. sidTypeInvalid = fi_SidTypeInvalid.GetValue(null);
  25. MethodInfo mi_GetAccountFromSid = typeof(PeopleEditor).GetMethod("GetAccountFromSid", BindingFlags.NonPublic | BindingFlags.Static);
  26. ntAccountName = mi_GetAccountFromSid.Invoke(null, new object[] { binaryForm, userName, domainName, sidTypeInvalid }) as string;
  27. //pe.GetAccountFromSid(binaryForm, out userName, out domainName, out sidUse)
  28. SecurityIdentifier sid2 = (SecurityIdentifier)new NTAccount(ntAccountName).Translate(typeof(SecurityIdentifier));
  29. Console.WriteLine("Account name found: {0}", ntAccountName);
  30. Console.WriteLine("{0} (SID after GetAccountFromSid)", sid2);

We can use the public instance methods of the PeopleEditor class from a console application as well, however, in this case we have to inject a dummy HTTP context first.

  1. // inject fake context
  2. HttpRequest request = new HttpRequest(string.Empty, web.Url, string.Empty);
  3. HttpResponse response = new HttpResponse(new System.IO.StreamWriter(new System.IO.MemoryStream()));
  4. HttpContext dummyContext = new HttpContext(request, response);
  5. dummyContext.Items["HttpHandlerSPWeb"] = web;
  6. HttpContext.Current = dummyContext;
  7. PeopleEditor pe = new PeopleEditor();
  8.  
  9. // method 1
  10. ArrayList resolvedAccounts = pe.ResolveAccountsByArrKeys(new string[] { "testuser" });
  11.  
  12. // method 2
  13. PickerEntity entity = new PickerEntity();
  14. entity.Key = "testuser";
  15. var entity2 = pe.ValidateEntity(entity);

I found that the source of the issue might be the Translate method of the NTAccount class. If I call this method first (or after a long while) with the account name (like ‘testuser’), the SID of the disabled account is returned. When calling the method with the full login name (like ‘DomainA\testuser’ or ‘DomainB\testuser’), the SID of the corresponding user is returned. However, when we call the method with the account name (like ‘testuser’) just after we called the method with the full login name (like ‘DomainA\testuser’), the SID of the active user is returned. It seems that there is a sort of caching implemented somewhere in these objects, but since they are rather complex, including calls into unmanaged code, I was not (yet) able to resolve the issue of user name resolution.

August 21, 2013

November 17, 2010

August 10, 2010

Blog at WordPress.com.