SharePoint: Why are active users returned by GetNonImportedObjects?

As discussed in my previous posts about user profile cleanup for SharePoint 2013 and SharePoint 2016, when using Active Directory Import, the profile cleanup process is a bit more manual as compared to FIM Sync. It consists of three steps that need to be done periodically to keep things cleaned up:

1. Run a Full import.

2. Run a “Purge”. Example:

(Get-spserviceapplication | ? {$_.typename -match "profile"}) | Set-SPProfileServiceApplication –PurgeNonImportedObjects $true

3. Let the My Site Cleanup Job do its thing.

The “Purge” doesn’t actually delete anything, or send emails (MySite Cleanup does those things). All it does is flip a flag on the user profile, marking it as ‘ready for deletion’. However, you probably want to know what exactly you’re purging before you do it. So you run GetNonImportedObjects and look at the output:

(Get-spserviceapplication | ? {$_.typename -match "profile"}) | Set-SPProfileServiceApplication -GetNonImportedObjects $true | out-file c:\NonImportedProfiles.txt

Within that output file, you may find some account names for users that you believe should not have their profiles removed. That’s where things get interesting.

To lay the groundwork for your investigation, I’ll refer you to my previous post SharePoint: All about non-imported user profiles.

Having read that, you now understand that these profiles are not being imported, and that’s why “GetNonImportedObjects” shows them. It’s usually one of three issues:

1. They’re in an OU that is not selected for import.

2. They’re subject to your import connection filter.

3. The rare “lastKnownParent” thing.

If you’ve double checked all of these and they don’t apply to your target user account(s), then it’s possible that the user account may be “stuck” in a state where it cannot be updated.

If you review the SharePoint ULS logs** during a Full import, you may see an error like this:

“More than one DN specified for the same profile.”

What can happen is there is one table in the Profile database that contains user data, and another table that holds a reference to their distinguished names (DN) and whether or not the user object was imported on the last Full import. The DN reference may be in a state where it’s “stale”, and cannot be updated.

Essentially, the import tries to create a new link to the user profile but cannot because the old link is in the way.

Note: I’m purposely being vague about the database tables to prevent you from trying to fix this with a direct SQL update, which is unsupported, and generally a bad idea.

I’ve seen this condition in situations where a user account has been deleted and re-created in Active Directory. The scenario can go something like this:

  • User is imported. Profile is created.
  • User leaves the company. Their account is deleted in Active Directory.
  • A Full import is run, and the user is marked as non-imported.
  • The “Purge” is not run, so the users profile never gets cleaned up in SharePoint.
  • The user comes back. They get a new account with the same account name. — Or if you have a lazy AD team, they may even reuse account names for completely different people.
  • The user profile is back “in scope” for the import, but cannot be updated because there is still the reference to the old DN that gets in the way.

Important: Recreating user accounts in Active Directory, using the same account name can cause other mysterious problems within SharePoint. See this: SharePoint: Users randomly lose permission – are deleted from site

What can be done?

You can clear the offending records out of the reference table. That way, during the next Full import, the reference data will be rebuilt and corrected.

To do that in a supported manner, you must toggle the synchronization type setting.

Go into the Central Administration | <Your User Profile Service Application> | Configure Synchronization Settings.

Choose “Enable External Identity Manager” and click ok.

Give it a minute to clear the reference table out.

Then go back in and switch it back to “Use SharePoint Active Directory Import”

Then you’ll need to run a Full import.

Note: This only clears out the reference data. It does not remove any profile data, import connection info, or anything of the like. You can think of it as “clearing a cache” of sorts. However, the standard precautions of temporarily disabling the My Site Cleanup Job and taking backups of the UPA databases still apply.

Tools:

** You can use this PowerShell to see which server in the farm ran the AD Import job. Just keep in mind that the timestamps returned are in UTC, so you’ll have to convert to your local time zone:

#Check which server has been running the AD import job:
$tjs = Get-SPTimerJob | ? {$_.displayname -match "ActiveDirectory"} 
foreach ($tj in $tjs)
{$tj.name
$tj.displayname
$tj.historyentries | select StartTime, EndTime, ServerName, status -first 10 | sort -Descending starttime}

And you can use the following PowerShell script to check if the accounts are Active in AD, which OU they currently exist in, and if the “lastKnownParent” property is set. Keep in mind that it only verifies the user can be found in Active Directory and that it is not disabled.

The users are not validated against the LDAP filter or OU selection configured for the profile import connection. However, it does output the users Distinguished Name, which you can manually check against your OU selection.

#CheckNonImportedProfilesAgainstAD.ps1
#Author: Joroar
#Date: 9/3/2019
#Disclaimer: This PowerShell script is a SAMPLE only and comes with no warranties expressed or implied.  Use at your own risk.
#Summary: Takes output file from -GetNonImportedObjects and looks users up in Active Directory to see if they still exist and are active (not disabled) there.
#Limitations: 
#1: Currently this does NOT attempt any lookup for SAML or FBA users. Only Windows accounts.
#2: It only verifies the user can be found in AD and that it's not disabled.  Users are NOT validated against the LDAP filter and OU selection on the import connection.
#Instructions: Just provide your values for the infile and outfile files.
#$infile: The text file containing profiles to check.  This is typically the file output from Set-SPProfileServiceApplication -GetNonImportedObjects
#$outlog: Log file for the results
$infile = Get-Content "C:\NonImportedProfiles.txt"
$outlog = "C:\CheckNonImportedProfilesAgainstAD.log"
Set-Content -Value $null -Path $outlog
[array]$DCString = "DC="
$profileManager = [Microsoft.Office.Server.UserProfiles.UserProfileManager]([Microsoft.Office.Server.ServerContext]::Default)
IF(!$profileManager)
{Write-Host -ForegroundColor Red "Failed to get a connection to the User Profile Service Application.  Please make sure your account has Full Control in service app permissions."
Break}
FOREACH($line in $infile) 
{
$AccountName =""
$user = ""
IF($line -like "User*")
    {
    "----------------------------------------" | Out-file $outlog -Append
    $AccountName = ($line.Substring(5))
    #Check if non-Windows auth profile:
    IF($AccountName -like '*|*') 
        {"Doing nothing with Trusted Provider or FBA account since it may not be an AD user: " + $AccountName | Out-file $outlog -Append}
    ELSE{
    #Assume its a Windows account and look up the user profile in the UPA to get the DN
    TRY {$up = $profileManager.GetUserProfile($AccountName)}
    CATCH [system.Exception] 
    {"ERROR looking up profile for user: " + $AccountName + " -- " + $_.Exception.Message | out-file $outlog -append
    Continue
    }
        #Get the domain name from the DN so we look up the user in the correct domain.
        $SPSDN = $up["SPS-DistinguishedName"].value
        $Split = $SPSDN.split($DCstring,2,'NONE')
        $DomainDN = $split[1]
        $SAMname = $AccountName.split('\')[1]
        $Root = [adsi] "LDAP://DC=$DomainDN"
        #Connect to the domain
        $Searcher = new-object System.DirectoryServices.DirectorySearcher($root)
            IF(!$Searcher)
            {Write-host -ForegroundColor Red "Connection to $DomainDN failed.  Likely a permission problem"}
            #LDAP call to find the user in AD
            $Searcher.filter = "(&(objectClass=user)(sAMAccountName=$SAMName))"
            $user = $Searcher.FindOne()
                IF($user)
                {#Output the user details
                "Account Name: " + $AccountName | Out-file $outlog -Append
                "SamAccountName: " + $user.properties.samaccountname | Out-file $outlog -Append
                "SharePoint DN: " + $SPSDN | Out-file $outlog -Append
                "Active Directory DN: " + $user.properties.distinguishedname | Out-file $outlog -Append
                "Display Name: " + $user.properties.displayname | Out-file $outlog -Append
                "Email: " + $user.properties.mail | Out-file $outlog -Append
                $LNP = $user.Properties.lastknownparent
                "Last Known Parent: " + $LNP | Out-file $outlog -Append
                #Call out if Last Known Parent is set
                IF ($LNP)
                {write-host -ForegroundColor Magenta "LastKnownParent set for $AccountName"
                "WARNING: LastKnownParent set for: " + $AccountName + " --> Double check OU selection."| Out-file $outlog -Append}
                #Check if the user is disabled in AD
                $suser = [adsi]"LDAP://$($user.properties.item(""distinguishedname""))"
                $uac=$suser.psbase.invokeget("useraccountcontrol")
                    IF($uac -band 0x2)
                    {"Account is DISABLED" | Out-file $outlog -Append }
                    ELSE
                    {"Account is ACTIVE" | Out-file $outlog -Append }  
                }
                ELSE
                #User was not found in AD
                {"SharePoint DN: " + $SPSDN | Out-file $outlog -Append}
    }
    }
}

Here’s a simplified version of the same script. It will only look up users that are in the same domain as the SharePoint servers, but it may work a bit better in that scenario:

#CheckNonImportedProfilesAgainstAD-SingleDomain.ps1
#Author: Joroar
#Date: 9/3/2019
#Summary: Takes output from -GetNonImportedObjects and looks users up in Active Directory to see if they still exist and are active there
#Limitations: 
#1 - Currently this does not attempt any lookup for SAML or FBA users. Only Windows accounts.
#2 - This can only look up users in the local SharePoint forest.  If the users are in a trusted Forest, it will NOT find them.
#Instructions: Just provide your values for the input and outfile files
#$infile: The text file containing profiles to check.  This is typically the file output from Set-SPProfileServiceApplication -GetNonImportedObjects
#$outlog: Log file for the results
$infile = Get-Content "C:\NonImportedProfiles.txt"
$outlog = "C:\CheckNonImportedProfilesAgainstAD-SingleDomain.log"
Set-Content -Value $null -Path $outlog
foreach ($line in $infile) 
{
$AccountName = ""
$user = ""
IF($line -like "User*")
    {
    "----------------------------------------" | Out-file $outlog -Append
    $AccountName = ($line.Substring(5))
    #Check if non-Winodows auth profile
    IF($AccountName -like '*|*') 
        {"Doing nothing with Trusted Provider or FBA account: " + $AccountName | Out-file $outlog -Append}
    ELSE{
    #Assume its a Windows account and look up in AD
    $UserName = $AccountName.split('\')[1]
    $user = ([adsisearcher]"samAccountName=$($UserName)").FindOne()
        IF($user)
        {"Account Name: " + $AccountName | Out-file $outlog -Append
        "SamAccountName: " + $user.properties.samaccountname | Out-file $outlog -Append
        "Distinguished Name: " + $user.properties.distinguishedname | Out-file $outlog -Append
        "Display Name: " + $user.properties.displayname | Out-file $outlog -Append
        "Email: " + $user.properties.mail | Out-file $outlog -Append
        $LNP = $user.Properties.lastknownparent
        "Last Known Parent: " + $LNP | Out-file $outlog -Append
        #Call out if Last Known Parent is set
        IF ($LNP)
        {write-host -ForegroundColor Magenta "LastKnownParent set for $AccountName"
        "WARNING: LastKnownParent set for: " + $AccountName + " --> Double check OU selection."| Out-file $outlog -Append}
        $suser = [adsi]"LDAP://$($user.properties.item(""distinguishedname""))"
        $uac=$suser.psbase.invokeget("useraccountcontrol")
            IF($uac -band 0x2)
            {"Account is DISABLED" | Out-file $outlog -Append }
            ELSE
            {"Account is ACTIVE" | Out-file $outlog -Append }  
        }
        ELSE
        #User was not found in AD
        {"User " + $AccountName + " was NOT found in Active Directory. Likely deleted or in a remote Forest." | Out-file $outlog -Append }
        }
    }
}


6 Comments

Add a Comment