PowerShell Workaround: “Replicate Directory Changes” Permissions in AD Required for SharePoint 2010 Profile Syncs

According to the SharePoint 2010 Communities FAQ, Microsoft is burdening SharePoint 2010 Administrators with new requirements to obtain Active Directory accounts with “Replicate Directory Changes” permissions because…

In order to interrogate AD about “what has changed since time xyz”, we need the replicate-directory-changes permissions on partitions being synchronized, for example the domain partition being synchronized.These permissions are needed in particular to be able to read data within the deleted objects container of the partition. Standard users do not have permissions to read the content of this container, and we cannot simply grant rights over that container to the synchronization account.

While this may be the cleanest way to go about things, it’s an unfortunate requirement for SharePoint admins within organizations which understandably frown upon making such concessions for service accounts, especially when the change wasn’t required for SharePoint 2007 farms.

Microsoft’s recommended architecture is to to use the published information from a centralized farm but some SharePoint administrators are on their own and do not have that luxury. I encountered this issue in a lab environment and was able to circumvent it by writing a quick and dirty script to add new users from AD and sync all SharePoint users with AD. This script only addresses one site collection and does not drill down to its subsites. Run it as often as you would run a regular sync.

# SharePoint <-> AD Group Sync
# netnerds.net
#
# What it does:
# Uses LDAP to get a list of users from a specific group in AD (in this case the "All Finance Users" group)
# Enables "Allow Unsafe Updates" for the given Site Collection then returns it to its previous state.
# Adds AD Users (but ignores nested AD groups) to the specified SharePoint Group
# Syncs all Site Users with Active Directory. This includes name changes, etc.

# 1,2,3 Go
$ldappath = "LDAP://CN=All Finance Users,OU=Finance,DC=icanhas,DC=net"
$spgroupname = "Icanhas Intranet Members"
$sitename= "http://sharepoint"
$domain = $env:USERDOMAIN

$adgroup = [ADSI]($ldappath)
$spsite = SPSite($sitename)
$rootweb = $spsite.rootweb
$spgroup = $rootweb.Groups[$spgroupname]

$allowUnsafeUpdates = $spsite.AllowUnsafeUpdates
$spsite.allowUnsafeUpdates = 1

foreach ($memberDN in $adgroup.member) {

$member = [ADSI]("LDAP://$memberDN")  | where {$_.properties.objectcategory -match "CN=Person"}
     if ($member){
               $username = $domain + "\" + $member.sAMAccountName
               $spsiteuser = $rootweb.EnsureUser($username)

               $spUser = $rootweb.AllUsers[$spsiteuser]
               $spgroup.AddUser($spsiteuser)
               }
}

$spsite.allowUnsafeUpdates = $allowUnsafeUpdates

$rootweb.dispose()
$spsite.dispose()

Get-SPUser –Web $sitename| Set-SPUser –SyncFromAD

 
Note the AD Sync on the last line. Surprisingly enough, PowerShell’s SyncFromAD works without explicitly allowing Replicate Directory Changes. To test this, first I tried a domain account with regular privileges then I went a step further and denied Replicate Directory Changes for that account and it still worked.

You may want to make additional changes such as: deleting users that no longer exist in AD, and replicating all changes to subsites.

Ahh, just good enough :)

Chrissy is a PowerShell MVP who has worked in IT for nearly 20 years, and currently serves as a Sr. Database Engineer in Belgium. Always an avid scripter, she attended the Monad session at Microsoft’s Professional Developers Conference in Los Angeles back in 2005 and has worked and played with PowerShell ever since. Chrissy is currently pursuing an MS in Systems Engineering at Regis University and helps maintain RealCajunRecipes.com in her spare time. She holds a number of certifications, including those relating to SQL Server, SuSE Linux, SharePoint and network security. She recently became co-lead of the SQL PASS PowerShell Virtual Chapter. You can follow her on Twitter at @cl.

Posted in Active Directory, PowerShell, SharePoint
3 comments on “PowerShell Workaround: “Replicate Directory Changes” Permissions in AD Required for SharePoint 2010 Profile Syncs
  1. T.Blankertz says:

    It's very interesting.

  2. Michal says:

    Hello,

    I’m not a powershell specialist but I have very interesting question (from my point of view).

    This is simply statment which change DisplayName for user inside SharePoint. I need use this statement because when user use claims authentication the login looks like [email protected]. And I would like to see normal name there . Powershell command : Set-SPUser –identity "some_user" -DisplayName "some_name" -web http://name_of_web

    This is script which take all users objects from some LDAP directory:

    $strFilter = "(objectCategory=User)"
    $objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://OU=test,dc=my,dc=web")
    $objSearcher = New-Object System.DirectoryServices.DirectorySearcher
    $objSearcher.SearchRoot = $objDomain
    $objSearcher.PageSize = 1000
    $objSearcher.Filter = $strFilter
    $objSearcher.SearchScope = "Subtree"
    $colProplist = "name","mail"
    foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}
    $colResults = $objSearcher.FindAll()
    foreach ($objResult in $colResults)
    { $objItem = $objResult.Properties
    $objItem.name
    $objItem.mail
    }

    and shows result : eg. AAAAAA BBBBBB
    [email protected]_email.com

    So script :

    $strFilter = "(objectCategory=User)"
    $objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://OU=test,dc=my,dc=web")
    $objSearcher = New-Object System.DirectoryServices.DirectorySearcher
    $objSearcher.SearchRoot = $objDomain
    $objSearcher.PageSize = 1000
    $objSearcher.Filter = $strFilter
    $objSearcher.SearchScope = "Subtree"
    $colProplist = "name","mail"
    foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}
    $colResults = $objSearcher.FindAll()
    foreach ($objResult in $colResults)
    { $objItem = $objResult.Properties
    $objItem.name
    $objItem.mail
    }

    give me all users inside OU=test ( for example 10 users) –> show me email and name like : AAAAAAA BBBBBBB, [email protected]_email.com

    next I use :

    $Users = Get-SPUser -Web http://claims.my.web | where { $_.userlogin -match "i:0#.f|ldapmember" }
    foreach ($user in $users) { Set-SPUser -identity $user.userlogin -DisplayName "TEST" -web http://claims.my.web}

    The first line get all users from site : claims.my.web for these users that login start from i:0#.f|ldapmember ( all users which use claims authentication).
    Second line foreach user taken from web set display name. As you see I use DisplayName “TEST” –> and here I have problem. How compare emails from AD script and from email taken from Get-SPUser statement??
    Because I need compare email (AD and for every user inisde $users) and if it’s the same than set some $DisplayName value which will be the same as $objItem.name (value taked from AD)

    Thanks a lot if you can help me.

    BR,

    Michal

    • Chrissy LeMaire says:

      Hey Michal,
      I agree; this is an interesting question! Iwas actually looking into building some similar code but ran out of time. Have you found any solutions yet? I'll get to this as soon as I have the time and let you know what I find.

Leave a Reply

Your email address will not be published. Required fields are marked *

*