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 = "<LDAP://" & strDefaultNC & ">;" & _
                              "(&(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.
<DefaultProperty("Text"), ToolboxData("<{0}:UserPhoto runat=server></{0}: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
    <Browsable(True), Category("Miscellaneous"), DefaultValue(_defaultText), WebPartStorage(Storage.Shared), FriendlyName("Users Name"), Description("Text Property")> _
    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)
        output.Write("<center><img src='_layouts/uImage.ashx?user=" & SPEncode.UrlEncode(UName) & "' /></center>")
    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.

<add verb="GET" path="uImage.ashx" type="WebParts.UserPhoto, WebParts, Version=, Culture=neutral, PublicKeyToken=5**************d" />

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!

If you find this article useful, buy me a beer!

Tags: , , , ,

Incoming links

Comments (48)

Do you have anything to say?

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