I discovered the new Windows user tile API

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.

48 thoughts on “I discovered the new Windows user tile API

  1. dtisher33

    Joco, thanks for posting this! It amazes me that Microsoft has not provided a way to automate the Windows User Tiles in Windows 7 but your work will be very helpful. I expect to be able to publish a finished command-line app that will take in the two args (username and pic) and set the user tile accordingly. Once that app is complete and tested, it will be published under the same GNU GPL accordingly. Will update once complete – this is a life saver for a project I am working on. Thanks again!

    1. Joco Post author

      Congratulations, you’re my first commenter! :) Glad you liked my discovery. Feel free to post a link here when your project is finished.

  2. Gai-jin

    Joco — This is brilliant. I’ve had a heck of a time finding any way to set the user image on the logon screen, and this sounds like it will do it. The dat files appear to be the same between 2008 and R2, there’s just no way to create them in R1. The api might not work on a 2008 server, but even if I have to create the tiles on an R2 server then copy the .dat files over to our 2008 terminal server, it would get the job done.

    Only one problem — I am not a programmer. I have no idea how to USE the source code you posted. If you or dtisher get something put together that makes this useable either from a form or a command line, please be sure to post back, I’d love to be able to use it!

  3. pverning

    Joco thanks for your blog, user tile API is brilliant. You have updated your post and put a sample application. For most, which are looking for this API, it is a little bit difficult to use this code. So here are some steps to taken, to make your own exe.
    Thanks again for your emails and good work!

    Steps to taken:

    Copy and paste the above code in notepad.
    Save the file as UTF8 and with an extention of cs.
    For example Tile_replace.cs and save it in c:\temp

    Install the .net framework version 2 if you don’t have it already done so.

    In a dos command go to the directory c:\temp
    Compile the code with the following command:
    c:\temp\> C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe Tile_replace.cs
    In c:\temp there wil be a exe with the name Tile_replace.exe
    when you execute the exe with the following command, the picture will be set.

    Tile_replace.exe yourdomain\loginname path\picture.jpg

  4. Pingback: Using Pictures from Active Directory | Oddvar Haaland Moe's Blog

  5. Michael Anthony

    Excellent post! You’ve pointed me in the right direction.

    I have a problem though. The default .dat files are empty, and I can’t seem to get past sam/sam in the registry – can I only do this programmatically?

    I’m writing an app that simply needs to read the current user-tile. Getting it from the temp directory, obviously, has no point.

    Any shell32.dll offset for retreiving the user tile?

    I’m actually willing to do anything to get it right now – even if it means writing in a foreign language, like C.

    1. Joco Post author

      In a domain environment the .dat files are THE place where the pictures are stored. If it’s empty, it means the user hasn’t chosen a picture yet (and c:\ProgramData\Microsoft\User Account Pictures\user.bmp is used instead). The moment you set the user tile to something, it’s stored in a .dat file, and it won’t be empty ever again.

      In order to read the SAM registry, you need to either run your program as system or impersonate system just once (I have a post about that, too) and give read access to a normal user. In a non-domain environment, SAM is the place to read the pictures.

      Feel free to reply if it doesn’t work, maybe we’ll figure something out. I just wouldn’t rush reverse engineering again because it seems to offer little benefit at the moment.

  6. Michael Anthony

    I’m sure it would work, but impersonation requires me to enter my username, domain, and password.

    I may be doing something wrong too – it throws an exception, (error 1385) saying I’m not granted access.

      1. Joco Post author

        Does it need to work on one machine or lots of them? Does it need to work in domain or non-domain environments? If you need it on only one non-domain machine, then give access rights on the SAM registry to your normal user. In domain environments, you don’t need any hacks either, just read the file. If you need it to work on lots of non-domain computers, that’s when you’d need either programmatic impersonation or the shell function to read the user tile.

        1. Michael Anthony

          It’s designed to run on local machines, but I assume people will try it on systems with a domain-based user.

          The app is meant to clone the logon screen, so it’s targeted at one system.

          I’ve just written a script that utilizes pfnMsgCallback(), and _wlx_notification_info, but I can’t get it to work. (It call s createProcessAsUser.)

    1. Joco Post author

      Like I said in my other post, it’s possible, but not reliable, you should be running a system service for best results.

      At a first glance, it appears that the shell32 function 261 can be used to get the user tile. However I have no time to investigate it now.

      1. Michael Anthony

        Got it! Should have thought it was 261.

        the function takes 4 args: LPCWSTR, UINT, LPWSTR, and ULONG, and obviously returns an hResult, dumping the data into LPWSTR (wchar).

        calling:
        getUserTilePath(null, 0×80000000, tmp, max_path) – tmp is the output wchar (array in my case).

        Real deal: the function creates the file normally found in the Temp directory, and then returning that same path.

        I guess it’s a good alternative to digging up SAM, or calling up UCP.

        thanks for the help! :)

  7. Michael Anthony

    Right, so now that that’s done, I’ve decided my app should also be able to change the user-tile.

    Unfortunately, I can’t seem to get it right, even after calling CoInitialize[Ex]();, and putting it in a thread.

    If I can’t get it right, can I ship my package with your compile C# app (I have compiled it under .NET 3.5, and it works like a charm).?

  8. Pingback: Using Pictures from Active Directory | MSitPros Blog

  9. Pingback: Setting the user tile image in Windows 7 and Server 2008 R2 « iammarkharrison

  10. iammarkharrisontoo

    I’ve taken your work and incorporated it into a minimalist powershell script that compiles the signature and sample application at runtime using the PowerShell Add-Type cmdlet allowing you to use this to load the image using a logon script. I’m currently working on using the thumbnailPhoto attribute in AD to populate the user tiles without needing anything other than .NET and Powershell.

    Thanks for all the help your article gave!

  11. Pingback: Jake's Blog » Loading a Windows 7 User Tile using the picture in Active Directory

    1. Joco Post author

      Any Windows debugger will do. If I remember correctly, I needed to find where it reads or writes the bitmap file, set a breakpoint and analyze the stack.

        1. Mike

          In Windows 8, have you devised a way to use the .dat user picture file on a domain controller (or a user thumbnail stored in Active Directory) to populate the user picture in a domain user’s profile on a domain client computer?

          1. Joco Post author

            The .dat is client specific, it’s not in the AD. It contains a picture of the user tile, but is not itself the picture file.

            Others and myself have written programs to fetch the image attribute from AD, and then I guess you just have to call the API I mentioned above to use it as the tile.

        2. Valter Anjos

          Hi Joco,

          Is there already a way to do that on Windows 8?

          I’m trying to do that but I’m not a C# / powershell expert so I couldn’t do it till the moment.

          Thanks a lot for your code..I’m using it for Win7 and it’s working fine :)

  12. Mike

    In Windows 9, w
    hen a domain user manually sets an account picture, it is stored in Users\Public\Public Account Pictures\{Sid} as 4 different jpg files (40×40, 96×96, 200×200, and 448×448). If one or more of these is edited manuallly, with Paint for example), that edit will take effect on the first subsequent logon.

    Additionally, account picture information is stored in %USERPPROFILE%\Account Pictures in a .accountpicture-ms file. The structure of this file may be similar to the .dat files used with domain user pictures in Windows 7

     

      1. Valter Anjos

        Hi Joko,

        I already red your response before but I was asking if there is an EXE to use on Win8 since I’m not a programmer and I don’t have enough skills to do that.

        Nevertheless I’ll trying to get something that works on win8.

        Thanks a lot.

  13. Pingback: Richard J Green

  14. JessePaxson

    Getting this thing to work in Windows 8 is a chore that I haven’t mastered yet. Trying to create a console application that uses Windows.System.Userprofile is tedious. And even when we get it to load, we can’t get it to do anything silently.

    Can’t wait to see some progress in the Windows 8 side of this…

    1. Chris Wright

      Glad its not just me that is finding this hard work. So frustrating that they don’t just provide a regular .NET library that lets you do this (or even a native Win32 API) and instead we have to mess around with these Windows Runtime APIs that are kind of supported in regular .NET apps and kind of not

  15. Zack

    Any possibility of creating a User Tile .exe for Windows 8? I have still yet to see one. Or better yet, a single .exe that works for both Windows 7 & 8.

    Thanks.

  16. Joco Post author

    Sorry guys. I really gave Windows 8 RC a try but I really hated it. There’s no way I’m going to install it ever. Apparently this Windows 7 API doesn’t exist in Windows 8, so whatever I discovered above, means nothing now. It’s not a surprise, since it never was supported in the first place. As I pointed out above, there seems to be an official API, the Windows.System.UserProfile.UserInformation class. I don’t have Windows 8 anymore and I’m sure not buying it, so there’s no way I could develop this thing.

    1. Chris Wright

      I’m not keen on Windows 8 either, but anyway I stuck a trial version of it on a VM and installed VS 2012 on there so that I could try this “supported” API but as someone else already mentioned: it is horrible trying to use these “Windows Store” specific APIs from a regular .NET application. Even when you do get the required references added (and manual project file edits… yep!) it turns out that because these APIs are intended to only be used from Windows Store apps, you can’t actually access files on the hard drive without the user selecting the file from a file/folder picker. So it seems not only did MS make a mess of the UI in Windows 8, they also made a mess for anyone wanting to develop for it. These new Windows Runtime APIs make no sense – they seem to be kind of based on .NET but not enough for you to be able to just use them like normal .NET methods. They’re all async methods so you can’t just call them normally but yet you also can’t use the Await keyword with them because VS will tell you they’re not awaitable. Fair enough if these APIs are only meant to be used by metro apps, but for the love of god Microsoft why would you make some things ONLY programmatically accessible through these APIs (like setting an account picture, as in our example). I think this annoys me even more than the fact that they think Powershell is a suitable API for programming against Exchange Server from .NET programs…

Leave a Reply