Photos in Active Directory

Posted by on November 9, 2006

The number one reason people come to this page is they want pictures in Outlook from the Global Address List.
It appears this is now possible using Outlook 2010 against Exchange 2010 as outlined by Ilse Van Criekinge in her Technet blog.

Oh my blog, how I’ve neglected you.

Here’s a couple of things I managed to cook up at work this week, again after spending an inordinate amount of time on Google looking for the answer and having to piece it together bit by bit.

The problem: how to store (and retrieve) staff photos in Active Directory. After the first twenty or so webpages I’d decided on using the jpegPhoto attribute. The secondary problem of getting the staff to submit to getting their pictures taken has already been solved by the new photopasses we’re all getting.

Warning: There are some technical considerations to batch importing a large number of pictures into your AD regarding your replication topology. If you had a domain controller at the end of a very slow/poor connection then this could tie up that connection for a prolonged period of time, and as such the pictures could best be stored in ADAM or some other structured content store.

Ok thats the warning out of the way, now on the cool stuff:

1) Resize the images down to an appropriate size, I used Irfanview to batch resize the massive digital camera pics down to about 200×200 pixels resulting in ~25kb files.
2) Save all the pics in a directory named “Firstname Lastname.jpg”
3) Run this vbscript with sufficient (Domain Admin?) rights.

Const ForReading = 1

InDir = "C:\Temp\Staff Photos"
Set fso = CreateObject("Scripting.FileSystemObject")

set oIADS = GetObject("LDAP://RootDSE")
strDefaultNC = oIADS.Get("defaultnamingcontext")

Set theConn = CreateObject("ADODB.Connection")
theConn.Provider = "ADsDSOObject"
theConn.Open "ADs Provider"

Set theCmd = CreateObject("ADODB.Command")
theCmd.ActiveConnection = theConn
Set objRecordSet = CreateObject("ADODB.Recordset")

For Each tFile In fso.GetFolder(InDir).Files
tName = tFile.Name
'Gets the persons Name from the file by stripping the extention.
tName = Left(tName, InStrRev(tName,".")-1)

'You may need to tweak this bit depending on your naming conventions.
strQuery = ";" & _
"(&(objectClass=person)(name=" & tName & "));name,adspath;subtree"
theCmd.CommandText = strQuery
Set objRS = theCmd.Execute

If objRS.RecordCount = 0 Then
MsgBox "Can't find account for " & tName
Set objUser = GetObject(objRS("adspath"))

ObjUser.Put "jpegPhoto", ReadByteArray(tFile.Path)
End If

'Stolen from
Function ReadByteArray(strFileName)
Const adTypeBinary = 1
Dim bin
Set bin = CreateObject("ADODB.Stream")
bin.Type = adTypeBinary
bin.LoadFromFile strFileName
ReadByteArray = bin.Read
End Function

Download: UserPhoto.vb

Ok, thats the import done. I’m not entirely sure that that is the most appropriate format to store the pictures in, but they can be retrieved with the code below.

As part of a project I’m working on I’m looking at the implementation of Sharepoint across various parts of our company, so lets create a SharePoint Webpart to display the pics!

My first problem was how to create a webpart to output an image, not link to an image. In the end, thanks mainly to Simon Tocker I managed to get it working.

Imports System
Imports System.ComponentModel
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Xml.Serialization
Imports Microsoft.SharePoint
Imports Microsoft.SharePoint.Utilities
Imports Microsoft.SharePoint.WebPartPages
Imports System.DirectoryServices
Imports System.Drawing

'Description for UserPhoto.
"), XmlRoot(Namespace:="WebParts")> _
Public Class UserPhoto
Inherits Microsoft.SharePoint.WebPartPages.WebPart
Implements System.Web.IHttpHandler

Private Const _defaultText As String = ""

Dim _name As String = _defaultText

' Dim imgCtrl As Image

Property [UName]() As String
Return _name
End Get

Set(ByVal Value As String)
_name = Value
End Set
End Property

'Render this Web Part to the output parameter specified.
Protected Overrides Sub RenderWebPart(ByVal output As System.Web.UI.HtmlTextWriter)

End Sub

Protected Overrides Sub EnsureChildControls()

End Sub

Public ReadOnly Property IsReusable() As Boolean Implements System.Web.IHttpHandler.IsReusable
Return True
End Get
End Property

Public Sub ProcessRequest(ByVal context As System.Web.HttpContext) Implements System.Web.IHttpHandler.ProcessRequest

Dim response As System.Web.HttpResponse = context.Response
Dim request As System.Web.HttpRequest = context.Request
Dim who As String
Dim outImg As System.Drawing.Image
Dim AuthUser As String = Right(request.ServerVariables("AUTH_USER"), Len(request.ServerVariables("AUTH_USER")) - InStr(request.ServerVariables("AUTH_USER"), "\"))
Dim ds As New DirectorySearcher("ldap://dc=example,dc=net")

If Not (request.QueryString("user") Is Nothing) Then
who = request.QueryString("user").ToString
End If
ds.Filter = "(&"
If who <> "" Then
ds.Filter &= "(name=" & who & ")"
ds.Filter &= "(sAMAccountname=" & AuthUser & ")"
End If
ds.Filter &= "(jpegPhoto=*))"
Dim res As SearchResult
res = ds.FindOne
If Not (res Is Nothing) Then
'Dim imgStr As String = res.Properties("jpegPhoto").Item(0)
Dim imgByte() As Byte = res.Properties("jpegPhoto").Item(0)
Dim ms As System.IO.MemoryStream
ms = New System.IO.MemoryStream(imgByte)
Dim newImage As System.Drawing.Image
newImage = System.Drawing.Image.FromStream(ms, True)
response.ContentType = "image/jpeg"
newImage.Save(response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg)
Dim msg As String = "No image for "

If who = "" Then
msg &= "you"
msg &= who
End If
outImg = DoImage(msg)
response.ContentType = "image/jpeg"
outImg.Save(response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg)

End If

Catch ex As Exception
outImg = DoImage(who & " : " & ex.Message)
response.ContentType = "image/jpeg"
outImg.Save(response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg)
End Try
Catch es As Exception
outImg = DoImage(who & " : " & es.Message)
response.ContentType = "image/jpeg"
outImg.Save(response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg)

End Try
End Sub

Public Function DoImage(ByVal msg As String, Optional ByVal w As Integer = 200, Optional ByVal h As Integer = 50) As System.Drawing.Image
Dim img As System.Drawing.Image
Dim g As Graphics

img = New Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format32bppArgb)

g = Graphics.FromImage(img)
g.FillRectangle(New SolidBrush(Color.White), 0, 0, img.Width, img.Height)
g.DrawString(msg, New Font("Verdana", 10.0F), New SolidBrush(Color.Black), 0, 0)
Return img
End Function

Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)

If UName <> "" Then
Me.Title = UName
Dim AuthUser As String = Right(context.Request.ServerVariables("AUTH_USER"), Len(context.Request.ServerVariables("AUTH_USER")) - InStr(context.Request.ServerVariables("AUTH_USER"), "\"))
Dim ds As New DirectorySearcher("ldap://dc=example,dc=net")
ds.Filter = "(sAMAccountname=" & AuthUser & ")"
Dim res As SearchResult
res = ds.FindOne
Me.Title = res.Properties("Display Name").Item(0)
Me.Title = "Your Photo"
End Try
End If
End Sub
End Class

Download: PhotoLoader.vbs

Additional to that .Net code, the assembly must be installed into the GAC, this made it pretty hard for me to debug the handler! I’m going to be splitting it into its own class as having the assembly installed in the GAC means it ignores the one I keep putting into /bin! Also, as mentioned by Simon you need to edit your sharepoint web.config and add a line to the httphandlers section.

I also had to set my Sharepoint trust level to Full as I kept getting a message about AllowPartiallyTrustedCallers() not being set, and I couldn’t for the life of me work out how to set it!

Tags: , , , ,

Incoming links

Comments (48)

Do you have anything to say?

Powered by Wordpress and Stripes Theme Entries (RSS) | Comments (RSS)