I recently discovered that you can now store user photos in an Active Directory and Outlook 2010 can display those photos. My next idea was that I wanted to use those photos as user tiles (profile pictures) in our Windows 7 and Server 2008 R2 installations. How do you do that programmatically? Here’s the road to my discovery.
So far…
This MSDN article only states the obvious: you can do it manually in the control panel. Someone blogged here and here on how to automate most parts of the process. It goes like this:
- You take a picture and resize it to 126×126.
- You create some binary data with a BMP and some additional fields. It’s a proprietary format but quite easy to reverse engineer, there’s a size field or two, some seemingly constant values and a Unicode string that points to the original location of the picture.
- In the case of a domain account, you write it to the file named c:\ProgramData\Microsoft\User Account Pictures\DOMAIN+username.dat. Otherwise you put it in the SAM registry at HKLM\SAM\SAM\Domains\Account\Users\########!UserTile, where the value of those hash marks comes from a Names subkey at the same place.
There’s a step missing: the new user tile data won’t be read by Windows right away, you need to reboot or relogin or something like that.
Debugging
This is where I grabbed my debugger to reverse engineer this. There’s a file named shacct.dll which does all the above steps and more. It notifies the shell when the user tile changes and it will be effective immediately. It does this by calling SHChangeNotify with parameters like this:
#include <shlobj.h> #include <stdlib.h> void main() { byte *data = (byte *)malloc(12); memset(data, 0, 12); data[0] = 10; data[2] = 11; SHChangeNotify(0x4000000, 0x3000, data, 0); } |
Change the user tile data in the filesystem or in the SAM, run this, and the very next time you look at the start menu, you’ll see the new picture. The data parameter is an SHITEMIDLIST, the first SHITEMID is 10 bytes long, contains the byte 11 followed by 0′s, and then comes the 2 bytes long terminator of 0′s. The first parameter is the constant SHCNE_EXTENDED_EVENT. MSDN says that this value is not used. Not publicly, that is. Until now, that is .
As a side-note, I wondered how to access the SAM properly. I didn’t actually need that because I wanted to use user tiles in a domain environment and this method is only needed for local accounts. I found that you have to use the SamSetInformationUser to do that. Sadly it’s completely undocumented, but you can read about a similar function on MSDN here.
Then I started wondering if there’s an even easier way, looked at the stack trace and then found the real deal: there’s a simple (again undocumented) DLL function to do all the above.
The real deal
The function is in shell32.dll. It doesn’t have a name but the ordinal 262. It takes a username (MACHINE\user or DOMAIN\user format), a zero (the usual reserved stuff, I guess), and a picture path (can be any well known format or size) parameter, and returns an HRESULT. If you pass a null username, then the result of GetUserNameEx with a NameSamCompatible parameter will be used. It uses COM inside, and only works on STA threads (otherwise throws an InvalidCastException (0×80004002, E_NOINTERFACE)). When you call it, it resizes the picture, stores it in the .dat file or the SAM and then notifies the shell. Here’s the C# signature and a sample application built around it:
using System; using System.Runtime.InteropServices; namespace FejesJoco { class Program { [DllImport("shell32.dll", EntryPoint = "#262", CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void SetUserTile(string username, int whatever, string picpath); [STAThread] static void Main(string[] args) { SetUserTile(args[0], 0, args[1]); } } } |
Just compile and run it from a command line. It expects two parameters, the username and the picture path, as described above.
I tested it on NT 6.0 (Windows Vista and Windows Server 2008) and 6.1 (Windows 7 and Windows Server 2008 R2), with domain and local users, multiple file formats and sizes, and it just works. The MSDN article cited above says that user tiles are new to Windows 7, and indeed, this function doesn’t exist on Windows XP. There are user pictures there, too, but who cares now? This new API is undocumented but I guess it’ll stick around, so feel free to use it!
UPDATE 2010.12.22.: I also included this function in my framework.
UPDATE 2011.02.10.: I received many requests about how to actually use this API. The plan is to include this and many other things in a general toolset or something, but it’s going slow. So in the meantime, I replaced the bare signature with a sample application, use it freely.