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!
November 9th, 2006 at 3:00 pm
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.
April 11th, 2007 at 10:36 am
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?
April 11th, 2007 at 10:48 am
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.
May 25th, 2007 at 8:52 am
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
August 30th, 2007 at 8:44 am
Is it possible to see the pics in the Global Address list?
September 18th, 2007 at 2:15 am
You mean in Outlook? Not to my knowledge.
October 25th, 2007 at 10:43 pm
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.
October 26th, 2007 at 9:55 am
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.
January 4th, 2008 at 8:25 am
Nice Work ! But i don’t know how to run vbscript in AD.
January 5th, 2008 at 1:20 am
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.
January 15th, 2008 at 1:03 pm
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.
January 16th, 2008 at 1:32 pm
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.
January 16th, 2008 at 3:58 pm
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.
January 16th, 2008 at 7:29 pm
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.
January 30th, 2008 at 5:44 am
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.
February 19th, 2008 at 12:55 am
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.
February 19th, 2008 at 7:37 am
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)
February 19th, 2008 at 11:35 am
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…
April 30th, 2008 at 10:39 am
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.
May 27th, 2008 at 10:47 pm
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.