Thursday, December 6, 2012

SharePoint 2010 Foundation User Profile Sync

Problem:

SharePoint 2010 Foundation does not provide a user profile synchronizing service between active directory and SharePoint. Sure, whenever a user logs into SharePoint it adds that user but it only copies the name and email address and only to the site collection in question. If there are changes to the name of the user, this can sometimes get stuck and not sync over correctly. Additionally, information like the manager, phone number, and other directory information does not get pushed to the user profiles.

Why does this matter?

When you use a Person/Group column in SharePoint 2007/2010, it is really just a lookup column to that site collection's User Information List and thus you can return any column from the user information list that you like (Name, email, etc.) even though the input is always the username and click the little Check Names box. This can allow for specialized lookups in things like InfoPath or special forms where you want to automatically know who the person's manager is whenever they add an item to a list.

Enter Powershell scripting

Oh no, NO NO NO: the word "scripting" made it into a blog about 'out-of-the-box' solutions!? HYPOCRITE. Anyway, Powershell is like the command prompt from windows, something geeks have been using for years to do things that would otherwise require lots of clicks. Powershell is ONLY for system administrators, not regular SharePoint people. But this type of problem is really more of an admin problem anyway.

Resolution:

I combined two blog entries: here and here to make a script that does the following:
  1. Creates a request to pull from active directory via LDAP (lightweight directory access protocol I think). This asks Active Directory (where all usernames/passwords/etc are stored for your company) to find all users they have. Now, if your company has a bajillion users, this would be bad, but it asks for their account name and just a few properties so it's not too terrible (and, hey, I have like 80 users to deal with it).
  2. Gets all the site collections from SharePoint
  3. For each site, try to run a simple "SyncFromAd" command to make sure that the user's automatic info is up-to-date
  4. Go the user information list and update the columns in there with my information
  5. Forgot to mention - I added a couple of custom columns to the user information list for my site collection...that may be a no-no, not totally sure ^_^, but I SET THEM ANYWAY. Bam.
So, without further ado, here's the script that does the work (and this WILL take updating on your end if you want to use it to specify the domain for your company and whether or not you are adding columns to your user information lists like I did):

$objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://OU=YOUR_USERS_OU,dc=YOUR_DOMAIN_NAME,dc=YOUR_DOMAIN_ENDING_LIKE_COM")
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
$varDomain = "YOUR_DOMAIN_NAME"

$colProplist = "samaccountname","title","department","ipphone","mobile","name","distinguishedname","manager"
foreach ($i in $colPropList) {
 $objSearcher.PropertiesToLoad.Add($i)
}

$colResults = $objSearcher.FindAll()
$manResults = $objSearcher.FindAll()
$sites = Get-SPSite -Limit "ALL"

foreach ($objResult in $colResults) {
 $objItem = $objResult.Properties
 write-host 
 $userID = $varDomain+"\"+[string]$objItem.samaccountname
 foreach ($site in $sites) {
  $web=$site.RootWeb
  set-spuser -Identity $userID -SyncFromAD -web $site.url -ErrorAction SilentlyContinue
  if(!$error[0]) {
   write-host $site.url " - " $userID
   $list = $web.Lists["User Information List"]
   $query = New-Object Microsoft.SharePoint.SPQuery
   $query.Query = "<Where><Eq><FieldRef Name='Name' /><Value Type='Text'>$userID</Value></Eq></Where>"
   foreach ($item in $list.GetItems($query)) {
    $item["JobTitle"] = [string]$objItem.title
    $item["Department"] = [string]$objItem.department
    $item["IPPhone"] = [string]$objItem.ipphone
    $item["MobilePhone"] = [string]$objItem.mobile
    $item["Title"]= [string]$objItem.name
    $item["Username"] = [string]$objItem.samaccountname
    $manager = $manResults | Where-Object {$_.Properties.distinguishedname -eq $objItem.manager}
    $managerClean = $varDomain+"\"+[string]$manager.Properties.samaccountname
    $spManager = Get-SPUser -Identity $managerClean -web $site.url
    $item["Manager"] = $spManager
    $item.SystemUpdate()
   }
  }
  else {
   #write-host $site.url " - " $error[0]
  }
  $error.clear()
  $web.Dispose()
  $site.Dispose()
 }
}

2 comments:

  1. This looks really neat - do you know if it can pull the thumbnailPhoto attribute as well?

    ReplyDelete
  2. I have looked, looked, and looked some more. Without editing the script to use something like the copy web service or something to add the byte stream of the picture then I don't think it can pull it. Now, you *might* be able to push a URL to a photo, maybe even a relative URL, but I have not attempted that.

    ReplyDelete