Arricc

November 9, 2006

Photos in Active Directory

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
    Else
      Set objUser = GetObject(objRS("adspath"))
      ObjUser.Put "jpegPhoto", ReadByteArray(tFile.Path)
      ObjUser.SetInfo
    End If
Next
'Stolen from http://www.ericphelps.com/q193998/index.htm
Function ReadByteArray(strFileName)
    Const adTypeBinary = 1
    Dim bin
    Set bin = CreateObject("ADODB.Stream")
    bin.Type = adTypeBinary
    bin.Open
    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
        Get
            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)
        EnsureChildControls()
        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
        Get
            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
        Try
            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 & ")"
            Else
                ds.Filter &= "(sAMAccountname=" & AuthUser & ")"
            End If
            ds.Filter &= "(jpegPhoto=*))"
            Dim res As SearchResult
            res = ds.FindOne
            Try
                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)
                Else
                    response.Clear()
                    Dim msg As String = "No image for "
                    If who = "" Then
                        msg &= "you"
                    Else
                        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
                response.Clear()
                outImg = DoImage(who & " : " & ex.Message)
                response.ContentType = "image/jpeg"
                outImg.Save(response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg)
            End Try
        Catch es As Exception
            response.Clear()
            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)
        MyBase.OnPreRender(e)
        If UName <> "" Then
            Me.Title = UName
        Else
            Try
                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)
            Catch
                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=1.0.0.0, 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!

I'm a big fan of my beers and ales. If you find this article useful, consider buying me a beer!

22 Responses to “Photos in Active Directory”

  1. Simon Says:

    Glad to be of help. Although I am still ont sure how “scary” the httphandler is yet.

    I’m working on a new usage of handlers for ajax proxys, even if this does was a lot of resources and loads the whole page again for an ajax call its worth it as its only the same that would happen on a postback.

    Long time ago I set security to full and gac’d everything where sharepoint was concerned, not the best solution but if your internal network only then less to worry about.

  2. khaled Says:

    hi
    i used the code for active directory picture.. and there is no error in it.
    but i cant see anything happened after this code. i opened active directory and i didnt find the picture. i opened outlook exchange and i didnt find anything for the user too.
    so what does this code do excatly?

  3. Fizzgig Says:

    The code for retrieving and displaying the pic is a Sharepoint WebPart.

    The standard AD tools don’t have any features for using pictures, even though the AD schema has properties for them.

  4. BigSte Says:

    hi
    I used the code for active directory picture.. and it worked a treat. I did have to change the picture names to suit our username convention but it loaded the JPGs straight into the AD. Using Softera LDAP Browser I can click on the jpegPhoto field and it’ll open the JPG in ACDSee.

    I don’t know what to do with UserPhoto.vb, I tried some things but got nowhere. I got lost after that, don’t really know what I’m doing tbh.
    Still, the photos in AD are good ;-)

  5. bruce Says:

    Is it possible to see the pics in the Global Address list?

  6. Fizzgig Says:

    You mean in Outlook? Not to my knowledge.

  7. craig Says:

    How do I know the upload is working?
    Also, can you give more info about the viewing of pictures… how do I put something into the GAC? What exactly needs to go there?
    Step by step info would be very helpful.
    Thanks.

  8. Fizzgig Says:

    You can check for something in the jpegPhoto attribute of users using adsiedit.

    GAC? Assuming you mean the GAL in Outlook, then no, there is no way I know of to get pictures to appear there.

  9. Ajay Chanana Says:

    Nice Work ! But i don’t know how to run vbscript in AD.

  10. Fizzgig Says:

    IIRC you just have to run it using a Domain Admin level account (or an account with sufficient delegated privs) as described, the script connects to the appropriate AD objects and updates the pics onto them.

  11. Yordanov Says:

    Hi.
    I am not so familiar with .NET, and i am wondering what exactly to do with this .NET code (UserPhoto.vb). I downloaded .NET Framework SDK to use gacutil, but there is no result. I think that, this code must be compiled or somethink ,i don’t know.
    Can anyone help me ?

    Thank’s in advance.

  12. Fizzgig Says:

    UserPhoto.vb is the code for a Sharepoint 2003 webpart. They can be really fiddly to install and debug. If you’re not using sharepoint you may want to modify the code to your own solution.

  13. Yordanov Says:

    Hi
    I know that userphoto.vb is the code. I tried to compile it with vbc(virtual basic compiler) but reports an error in the compilation process :
    “warning
    BC40056: Namespace or type specified in the Imports ‘Microsoft.SharePoint’ does
    n’t contain any public member or cannot be found. Make sure the namespace or the
    type is defined and contains at least one public member. Make sure the imported
    element name doesn’t use any aliases. I

    Imports Microsoft.SharePoint

    There is installed .net framework SDK on the server.The server is with Windows Server 2003 64bit and the SDK is 64bit too.There is installed Share point serveces 64bit. I can’t understend what is wrong. I looked in c:\windows\assembly and find out that “Microsoft.Sharepint” assembly exists.

    I don’t know what to do.

  14. Yordanov Says:

    Hi again.
    I compiled the dll but now when i try to install it ito the GAC the result is
    “Failure adding assembly to the cache: Attempt to install an assembly without a strong name”

    What is the Strong name of the assembly and how to sign it ?
    If anyone know, please help me because i am almost done.

  15. Limbert Says:

    Yordanov,
    If u r using Visual Studio 2005 (or VS2008) use the command:
    sn -k c:\keyfile.snk
    where the “keyfile.snk” its the output file, now this file must be use in your application (Visual Studio) using the IDE go to Project, Properties, Signing, clic on the checkbox “Sign the assembly” on the textbox choose your recently created “keyfile.snk”
    Compile the entire project, now your assembly has an strong name.

  16. Mike McCormack Says:

    Hello Arricc:

    I am trying to implement your code on a MOSS system. Everything compiles, installs on “http://iplacer:4001/” etc. But I keep getting a “File Not Found” page display on the page http://iplacer:4001/_layouts/MySite.aspx when I try to open my users MySite. When I take out the web.config “HTTP Handlers” line the page again opens normally. I just can’t figure out what “file” is not being found. I’ve redone everything several times and am stuck. Any suggestions? Thanks in advance.

  17. Mike McCormack Says:

    On #16, I was a bit worn out. I’m using the “UserPhoto” vb code to obtain photos already installed in our AD “jpegphoto” field. I got the complete error dump which follows:

    File Not Found. at System.RuntimeTypeHandle._GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark, Boolean loadTypeFromPartialName) at System.RuntimeTypeHandle.GetTypeByName(String name, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark) at System.RuntimeType.PrivateGetType(String typeName, Boolean throwOnError, Boolean ignoreCase, Boolean reflectionOnly, StackCrawlMark& stackMark) at System.Type.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase) at System.Web.Compilation.BuildManager.GetType(String typeName, Boolean throwOnError, Boolean ignoreCase) at System.Web.Configuration.ConfigUtil.GetType(String typeName, String propertyName, ConfigurationElement configElement, XmlNode node, Boolean checkAptcaBit, Boolean ignoreCase)

  18. Fizzgig Says:

    Afraid I can’t help much with this anymore as I’ve not done any work with Sharepoint in a while…

    The only thing I do remember is having problems with the security model and having to drop all the permission levels way down to allow stuff to work…

  19. Oddvar Says:

    Interesting page you have here.
    I did a similar project with pictures in Active Directory users and computers. You can see the results on my webpage: www.moe.am/blog
    I managed to get pictures displayed directly in ADUC.

  20. Oddvar Says:

    Forgot to create a direct link: http://www.moe.am/blog/?p=8
    Here you will find information on how to enable ADUC for pictures.

  21. AD user entries: photos included? | keyongtech Says:

    […] Re: AD user entries: photos included? Hello Steve, Have a look in this articles: http://www.arricc.net/active-directo…sharepoint.php http://www.moe.am/blog/?p=8 Best regards Meinolf Weber Disclaimer: This posting is provided "AS IS" with no warranties, and confers no rights. ** Please do NOT email, only reply to Newsgroups ** HELP us help YOU!!! http://www.blakjak.demon.co.uk/mul_crss.htm > The question: When using Active Directory to manage user accounts on > the network, does it provide for including a photo of the user as part > of the AD record? Outlook does, so I was curious if AD did so also. > > The reason: Aside from general curiosity, I thought that it might be a > good security measure in large companies. One such scenario: user guy > comes in and asks for this or that to be changed, so help desk guy has > a photo on the AD record to make sure that the guy asking for > such-and-such is the real person associated with that AD record. > > Thanks in advance. > > //Steve// > > Steve Silverwood, KB6OJS > Email: kb6ojs > Web: http://kb6ojs.co […]

  22. Andrew Says:

    Dudes, the Address Book / Global Address List in Outlook 2007 has a built-in viewer for jpegs. When you double-click on a particular user to see their contact details, the jpeg will display as well (if you have populated the user attribute with the data / photo). Along with the vbscript code at the top of this page (which I gratefully acknowledge), your work is done! (All for the cost of upgrading to Outlook 2007, which has other benefits as well). If upgrading to outlook 2007 isnt an option, for $300 you can buy a web-page which you can publish on your corprate intranet - do a seach on Directory Manager, the company is Ithicos, they have 3 products for directory searching and updating that are dirt cheap ($300 each), brain-dead simple to install and configure, and really do the job as advertised (plus display the photos). Go ahead - make my day!

Leave a Reply