In my former post I’ve illustrated how to synchronize user images between AD and SharePoint using a custom web part in a self-service way. If you need to synchronize user images automatically you need a more advanced technique.
The user profile synchronization service in SharePoint provides a built-in solution for you, but only if you need one-way (SharePoint to AD) synchronization of images, as described in this article:
If you would like the user images to be updated automatically in AD and SharePoint when it is changed on either end of the connection you need to apply some tricks.
(REMARK: Most of the codes for this solution are borrowed from my former post, hence I’ve include only the most relevant new snippets in the current post. The entire solution can be downloaded from here, use it at your own risk. Any feedback is welcome.)
To fulfill the requirement I’ve created a custom timer job that runs periodically and checks the images for all users in both AD and SharePoint, and if these images are different (missing on either sides or has different hash value) then the the more recent image is copied over the older one into the other system.
(REMARK: In this post I do not concentrate on the custom timer job development, just on the actual synchronization task. If you are new to timer jobs, you can find several excellent posts about this topic on the web, like this one from Andrew Connell:
But the question is: how to get the date when the image is modified in the AD and in the SharePoint profile?
It was lot easier (at least, for me) to answer the second one: the images stored in a dedicated list in the SharePoint My Site Host, so we can read the last modified date using standard SharePoint API.
After some investigation in AD and on the web I’ve found the answer to the first part as well. The LastOriginatingChangeTime property of the AttributeMetadata class (that can be accesses through the ActiveDirectoryReplicationMetadata object) gives you information about when a specific AD property was changed.
The following code snippet from the helper class ADUtils illustrates the concept:
The ADUtils class is initialized like this:
If you read my former post in the topic you might remember that there was a web part property that stored the AD root partition. In the meantime I found a direct way to get this AD path from the short domain name:
We can access the AD photo via the ThumbnailPhoto property of the ADUtils class:
I’ve created a static helper method to access profile properties easier:
After this short introduction to the code environment, let’s see the code that is responsible for the synchronization logic. In the SynchronizePhotos method we iterate through all users in the profile manager, check the user images both in AD and in the SharePoint profile, and act if the images are different:
Before trying out the job, be sure that the SharePoint 2010 Timer service (SPTimerV4, OWSTIMER.EXE) is running, and the service account has write rights to alter Active Directory properties. For the sake of simplicity my service account has domain admin rights, but if you would like to set the permissions as recommended I suggest you to read this post, especially the section Nice, so what about actually writing back to AD (Sync)?:
If you change the code it is important to restart the SharePoint 2010 Timer otherwise the new assembly won’t be loaded by the service.
The next image shows the timer job after deployment on the list of scheduled jobs:
If you click on the name of the job, the details are displayed. If you don’t want to wait for the job to be started automatically, you can click Run Now.
By clicking Run Now you will be navigated back to the list of scheduled jobs. If you would like to run the job once more but would not like to look up the job in the long list, simply click Back in Internet Explorer or use the Alt + Left Arrow key combination. After you re-deploy the solution you need to refresh the page in IE (use F5).
The code that reads and writes (copies to be exact) user images in SharePoint and AD are the same (or at least, very similar) to the previous version you can find in my former post.
As you can see, I’ve included plenty of trace commands to make it easier to track what happens during timer job installation and execution, for example by using DebugView. If you would like to follow the execution step-by-step, you can debug the timer job. Don’t forget to attach the debugger to OWSTIMER.EXE. You may find my Visual Studio extensions useful in this case.
You can test the results and modify the image for a user in SharePoint using the Manage User Profiles admin page on the Central Administration site.
If you do any changes in the profile, do not forget to click Save and Close on the top of the page to persist your changes.
To do the same in Active Directory I suggest you to install this extension that enables Active Directory Users and Computers to manage the thumbnailPhoto property a user friendly way:
Again, if you change the thumbnail, press Apply or OK.
Known issues, limitations:
If you have read the code thoughtfully, you might have already noticed that it does handle a special case the wrong way. If the same image exists both is SharePoint and AD, but deleted later in SharePoint, one can expect that the corresponding image will be deleted in the AD as well. Instead of this, the image from the AD will be copied back to SharePoint again.
The reason of this behavior is that there is no information in SharePoint (unless one would like to include audit data into the game) about when the images were deleted or when the PictureUrl property was set last time in the profile. In lack of this information we can not decide which of the systems is the current one, and we choose the action that does not cause data lost, copy the AD image to SharePoint, instead of deleting the AD image as well.
If we really need this functionality, we can replace the timer job with an event receiver that synchronize (update or delete) the corresponding AD image immediately when the SharePoint image has been changed or deleted.
This type of working would be complete by using AD change notification mechanism on the other side, something that described here:
Since these methods do not fit well into the timer job that is the topic of the current post, I ignore them for now. Maybe in a later post…