jump to navigation

UserTile Automation June 23, 2010

Posted by Micah Rowland in USMT, VBscript, Windows 7.
trackback

Recently a customer requested that two accounts be created during the MDT process and required each to have a preset user account picture (referred to by Windows as the User Tile). There are a number of ways to accomplish this, USMT/Easy Transfer wizard being the easiest. However, due to the processes they already had built into the base image, I decided the best tack was to investigate the actual process employed in storing the User Tile and build automation that could quickly accomodate changes to the specifications of which image to use. After a quick search of the web, it seems that this particular problem, programatically changing the User Tile has not yet found a solution. I present to you my findings along with a sample script to tackle this seemingly simple manual process.

The first step in automating any process is to do it manually first and watch what happens. I rely on the Sysinternals tool Process Monitor as well as Process Explorer for most of my research. Launching Process Explorer as an administrator led me down a few dead ends. It was only after running Process Explorer in the System context using PSExec with the -s and -i switches that I was able to locate the location that Windows 7 uses to store the user tile.

The User Tiles configuration information is stored in Windows registry at HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\########, where ######## is a unique 8 digit hexadecimal ID, in as a binary value named UserTile. But how can we cross reference a username to this 8-digit ID? Glad you asked!

Taking a further look at the registry key, you’ll notice that beneath the user ids, there is another key called Names. If you expand this key you’ll see a list of all local user accounts on the system. Upon opening any of the user keys you’ll notices that only a default value exists. However, take a look at the value type… It is not a standard type, but a hexidecimal number 0x### where the ### id crossreferences nicely with the list of user ids above. In our script we need only pad this value with leading 0’s until it reaches the desired 8-digits. Trying to retrieve this hexidecimal “type” throws exceptions in nearly every method I tried. Exporting the key to a .reg file gave me the output I needed to be able to search for a specific username and retrieve its id.

The SAM Hive of the registry is not generally accessible to any user accounts and is configured, by default, to only be accessed by the System account. To maintain security, both the read and the write operations necessary to modify the UserTiles programmatically are accomplished by executing the operations in the System context. Execution in the system context is accomplished by utilizing the PSExec tool.

When a user interactively changes the UserTile by means of the User Account control panel, Windows resizes the image specified by the user to 128×128 and saves new image as a 24-bit bitmap file in the C:\ProgramData\Microsoft\User Account Pictures\ folder as USERNAME.bmp. The bitmap is then stored in the registry in the SAM and the user’s contact card is updated.

The binary data is composed of a header followed by a payload containing the binary graphic used for the UserTile and closed with a footer that contains the path to the file used as the UserTile. The header is 16 bytes long.

  1. 12-bytes (seem to be constant at 01 00 00 00 03 00 00 00 01 00 00 00)
  2. 4-byte field representing the size of the payload

The payload data reveals that the image stored in the registry is 126×126 pixels, presumably to make up for the 1 pixel wide border around the image when displayed on the logon screen and on the start menu. Further, the image is stored in 16-bit color depth using BI_BITFIELDS compression.

The footer contains the type of image file used and the location of the file used using Unicode (2-bytes per character). The format is as follows:

  1. 4-byte field (purpose unknown, possibly the length of the following field)
  2. A null-terminated Unicode string representing the file type
    1. Eg. “bmp” = 62 00 6D 00 70 00 00 00
  3. 4-byte field (purpose unknown, always 02 00 00 00)
  4. 4-byte field representing the payload (bitmap) size in bytes
  5. A null-terminated Unicode string representing the file location padded to the nearest 4 bytes.

Phew! Needless to say, this level of detail is not necessary for part 1 of this post, however in crafting a truely dynamic programmatic approach to changing the uer tile, we will need this information.

Solution:

It should be possible to engineer an application that accepts a username and a bitmap file to use. However, for part 1, it is simpler to export the registry keys from a sample machine using either the reg.exe or the regedit.exe utility under the System context using PSExec.

When scripting this automation, we must be aware that the first time PSExec is run, a EULA must be accepted. To avoid this, the following registry file is imported (AcceptPSExecEULA.reg):

Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Sysinternals\PsExec]
"EulaAccepted"=dword:00000001

The following script was created to accomplish the goal of changing the UserTiles:

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'   Title:   User Tile Change Script
'   Author:  Micah Rowland (Xtreme Consulting)
'   Date:    07/14/2010
'   Desc:    This script is designed to programatically replace
'            a single local user's User Tile on Windows Vista
'            and above.
'   Prereq:  This script requires the use of PSExec available
'            from http://live.sysinternals.com/psexec.exe
'   Usage:   UserTile.vbs USERNAME USERTILEFILE
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
CONST FORREADING = 1
CONST FORWRITING = 2

set objShell = CreateObject("Wscript.Shell")
set objFSO = CreateObject("Scripting.FileSystemObject")
set objArgs = Wscript.Arguments
set objShell = CreateObject("Wscript.shell")
if CheckArgs() <> "" then
     Wscript.echo Checkargs() & vbcrlf
     wscript.echo "Arguments Invalid. Usage: ChangeUserPicture.vbs USERNAME UserTileFile"
     wscript.quit()
end if

strUsername = objArgs(0)
strUserTileFile = objArgs(1)
strUserIndex = GetUserIndex()
set objFile = objFSO.GetFile(strUserTileFile)
set objTS = objFile.OpenAsTextStream(1)
strUserTile = objTS.ReadAll
wscript.echo strUserTile
strRegFile = objShell.ExpandEnvironmentStrings("%temp%") & "\UserIndexes2.reg"
set objRegFile = objFSO.OpenTextFile(strRegFile, ForWriting, true)
contents = "Windows Registry Editor Version 5.00" & vbcrlf & vbcrlf & "[HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\" & strUserIndex & "]" & vbcrlf & strUserTile
objRegFile.Write contents
objRegFile.Close

strImport = "PSEXEC -S -I reg import " & strRegFile
objShell.Run strImport

Function GetUserIndex

     ' This function exports the Names key of the SAM from the registry to determine a match between the user account provided
     ' and its hex ID for use in creating the new registry file for import. Because the .reg file is exported in Unicode format
     ' we use the type command to pipe it from stdout to a new text file.

     strUsername = objArgs(0)
     strUserTileFile = objArgs(1)
     objShell.run "cmd /c reg export HKLM\SAM\SAM\Domains\Account\Users\Names %temp%\UserIndexes.reg /y"
     wscript.sleep(500)
     objShell.run "CMD /C type %temp%\userindexes.reg > %temp%\userindexes.txt"
     wscript.sleep(500)
     set objFile = objFSO.GetFile(objShell.ExpandEnvironmentStrings("%temp%") & "\userindexes.txt")
     set objRegExport = objFile.OpenAsTextStream(FORREADING)
     curLine = objRegExport.ReadLine()
     do until instr(lcase(curLine), lcase(strUserName)) or objRegExport.atendofStream
          curLine = objRegExport.ReadLine
     loop
     if objRegExport.AtEndOfStream then
          wscript.echo "Username not found."
          wscript.quit()
     else
          curLine = ObjRegExport.ReadLine
          tmpGetUserIndex = mid(curLine, instr(curLine, "(") + 1, len(curline) - instr(curline, "(") - (len(curLine) - instr(curline, ")")+1))
          do until len(tmpGetUserIndex) = 8
               tmpGetUserIndex = "0" & tmpGetUserIndex
          loop
          GetUserIndex = tmpGetUserIndex
     end if
End Function

Function CheckArgs()
     ' This function makes sure that 2 arguments were provided and that the filename provided exists
     if objArgs.Count <> 2 then
          CheckArgs = "Exactly 2 arguments must be specified."
     elseif not objFSO.FileExists(objArgs(1)) then
          CheckArgs = "File not found."
     else
          CheckArgs = ""
     end if
 End Function

The script syntax is: SetUserTile.vbs USERNAME FILE

The file used by this script consists of only the UserTile value from a registry export of a preconfigured UserTile. This can be accomplished by setting a local user account’s picture, opening regedit using the psexec -s -i regedit.exe command, navigating to the SAM key mentioned above, determining the correct user account as detailed above, and exporting the key. Then remove all data in the .reg file except the “UserTile=hex:…” entry.

To leverage this script, a command-script file was created and added to the Software Installation task sequence.

reg import AcceptPSExecEULA.reg
cscript SetUserTile.vbs "USERNAME" DATAFILE.txt

I hope to have a commandline based program developed in the future to tackle this which will accept any sized bitmap image as it’s input as opposed to using a captured registry value. I hope you have enjoyed this post. If you have any questions feel free to ask!

Advertisements

Comments»

1. Anonymous - August 9, 2012

[…] […]

2. Handling UserProfile Picture Sync In VDI (Citrix UPM) « Siva Mulpuru's Blog - January 21, 2012
3. Using Pictures from Active Directory | MSitPros Blog - December 14, 2011

[…] must also give credz to xtremedeployment for the reverse engineering of usertile: http://deployment.xtremeconsulting.com/2010/06/23/usertile-automation-part-1/ (Great article) Pictures in Outlook/Exchange: – […]

4. Ariel - November 14, 2011

I’m having the error code 800A003E executing the script. Any help?

5. vbfox - October 17, 2011

If anyone is interested I used the information of this post and some trial and error to make a program able to read and change the user tile in C#.

The source code and binaries are on github : https://github.com/vbfox/UserTile

It’s not (yet?) able to generate a .reg file or to save the binary data in a file (for domain usage) but as the basic binary generation is there it shouldn’t be hard to add.

6. (2011-06-14) Pictures/Photos In Active Directory « Jorge's Quest For Knowledge! - August 7, 2011

[…] UserTile Automation […]

7. Jorge 's Quest For Knowledge! : Pictures/Photos in Active Directory - June 14, 2011

[…] the pictures/photos in AD and show them in the Windows 7 logon screen and start menu (using EXE) UserTile Automation Explains how to leverage the pictures/photos in AD and show them in the Windows 7 logon screen and […]

8. prairie_sailor - March 22, 2011

This script looks great for 1/2 of what I’m trying to do – in my case I’d like to take the tile from account1 on the same computer and set it to newly created account2 – all in script. While your script gets me a way to set the tile for account2 is there a way to get the picture from account1 in script?

Micah Rowland - March 23, 2011

Pretty simple, just read the registry key from user1 and save it to user2…. Something like this should get you on your way.

We’re basically providing a from and to username. It finds the sam key for each account, then reads in the usertile from the from and sets the usertile of the “to” account to the same value by generating a reg file to import

Remember to launch this using the system context by using
a psexec -s -i “cscript.exe myscript.vbs” commandline.


''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Title: User Tile Change Script
' Author: Micah Rowland (Xtreme Consulting)
' Date: 07/14/2010
' Desc: This script is designed to programatically replace
' a single local user's User Tile on Windows Vista
' and above.
' Prereq: This script requires the use of PSExec available
' from http://live.sysinternals.com/psexec.exe
' Usage: UserTile.vbs USERNAME USERTILEFILE
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
CONST FORREADING = 1
CONST FORWRITING = 2

set objShell = CreateObject("Wscript.Shell")
set objFSO = CreateObject("Scripting.FileSystemObject")
set objArgs = Wscript.Arguments
set objShell = CreateObject("Wscript.shell")
if CheckArgs() "" then
Wscript.echo Checkargs() & vbcrlf
wscript.echo "Arguments Invalid. Usage: ChangeUserPicture.vbs USERNAME UserTileFile"
wscript.quit()
end if

strUsernameFrom = objArgs(0)
strUsernameTo = objArgs(1)
strUserIndexFrom = GetUserIndex(strUsernameFrom)
strUserIndexTo = GetUserIndex(strUsernameTo)

const HKEY_LOCAL_MACHINE = &H80000002
strKeyPathFrom = "SAM\SAM\Domains\Account\Users\" & strUserIndexFrom
strKeyPathTo = "SAM\SAM\Domains\Account\Users\" & strUserIndexTo
strValueName = "UserTile"
strComputer = "."
Set oReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")

strcmd = "cmd /c reg export HKLM\" & strKeyPathFrom & " %temp%\UserTileFrom.reg /y"
objShell.run strcmd
objShell.run "CMD /C type %temp%\userTileFrom.reg > %temp%\usertilefrom.txt"

strRegFile = objShell.ExpandEnvironmentStrings("%temp%") & "\UserTileFrom.txt"
set objRegFile = objFSO.OpenTextFile(strRegFile, ForReading, true)
curline = ""
contents = ""

do
curline = objRegFile.ReadLine
wscript.echo left(curline,10)
loop while not left(curline,10) = """UserTile"""

if not objRegFile.AtEndOfStream then
set objFile = objFSO.CreateTextFile(objShell.ExpandEnvironmentStrings("%temp%") & "\UserTileTo.reg", true)
objFile.Write "Windows Registry Editor Version 5.00" & vbcrlf & vbcrlf & "[HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\" &strUserIndexTo & "]" & vbcrlf
do
objFile.WriteLine curline
curline= objRegFile.ReadLine
loop while not objRegFile.AtEndOfStream and not left(curline,1) = """"
end if

contents= "Windows Registry Editor Version 5.00" & vbcrlf & vbcrlf & "[HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Account\Users\" &strUserIndexTo & "]" & vbcrlf & contents

objFile.Close

objShell.Run "CMD /c reg import %temp%\UserTileTo.reg"

Function GetUserIndex(strUserName)

' This function exports the Names key of the SAM from the registry to determine a match between the user account provided
' and its hex ID for use in creating the new registry file for import. Because the .reg file is exported in Unicode format
' we use the type command to pipe it from stdout to a new text file.

objShell.run "cmd /C reg export HKLM\SAM\SAM\Domains\Account\Users\Names %temp%\UserIndexes.reg /y"
wscript.sleep(500)
objShell.run "CMD /C type %temp%\userindexes.reg > %temp%\userindexes.txt"
wscript.sleep(500)
set objFile = objFSO.GetFile(objShell.ExpandEnvironmentStrings("%temp%") & "\userindexes.txt")
set objRegExport = objFile.OpenAsTextStream(FORREADING)
curLine = objRegExport.ReadLine()
do until instr(lcase(curLine), lcase(strUserName)) or objRegExport.atendofStream
curLine = objRegExport.ReadLine
loop
if objRegExport.AtEndOfStream then
wscript.echo "Username not found."
wscript.quit()
else
curLine = ObjRegExport.ReadLine
tmpGetUserIndex = mid(curLine, instr(curLine, "(") + 1, len(curline) - instr(curline, "(") - (len(curLine) - instr(curline, ")")+1))
do until len(tmpGetUserIndex) = 8
tmpGetUserIndex = "0" & tmpGetUserIndex
loop
GetUserIndex = tmpGetUserIndex
end if
End Function

Function CheckArgs()
' This function makes sure that 2 arguments were provided and that the filename provided exists
if objArgs.Count 2 then
CheckArgs = "Exactly 2 arguments must be specified."
else
CheckArgs = ""
end if
End Function

9. Using Pictures from Active Directory | Oddvar Haaland Moe's Blog - February 23, 2011

[…] must also give credz to xtremedeployment for the reverse engineering of usertile: http://deployment.xtremeconsulting.com/2010/06/23/usertile-automation-part-1/ (Great article) Pictures in Outlook/Exchange: – […]

10. I discovered the new Windows user tile API | Joco blog - December 6, 2010

[…] MSDN article only states the obvious: you can do it manually in the control panel. Someone blogged here and here on how to automate some parts of the process. The part of the process which is covered in […]

11. Ezio - November 21, 2010

Hi all!
Thanks for the script!

I suggest this command instead of creating AcceptPSExecEULA.reg, with “reg add” you need only:

reg add HKEY_CURRENT_USER\Software\Sysinternals\PsExec /t REG_DWORD /v EulaAccepted /d 1

Bye from Italy!
Ezio Lacandia

12. Kristofer Olafsson - October 21, 2010

I made a VBscript that reads the thumbnailphoto atribute from AD (which outlook 2010 uses for the photo) and then saves the binary file as a bmp. however I haven’t quite figured out how to set it as the User Tile which is my goal. This script might get me there.

13. Shashidhar - July 27, 2010

I would like to change account picture for user “u1” on my WIndows 7 ultimate to usertile40.bmp which is located in “C:\ProgramData\Microsoft\User Account Pictures\Default Pictures\”.So i copied the psexec.exe to system32 and ran once with accepting eula so that accepteula registry get created. Then i copied your code and saved in C:\ as “ChangeUserPicture.vbs” and ran a command – cscript.exe “C:\ChangeUserPicture.vbs” “u1” “C:\ProgramData\Microsoft\User Account Pictures\Default Pictures\usertile40.bmp”. When i ran this comand i get the error “C:\ChangeUserPicture.vbs Microsoft runtime error: Input past end of file”. I checked temp directory but userindexes.txt is created with no contents (0 bytes). So can you help me in resolving this error?

Micah Rowland - August 28, 2010

Shashidhar,

Sorry for the long delay. The script currently is not designed to accept a bitmap file directly, as that would require logic to resize and reincode the bitmap in the format specified above. I do intend to write a small application to handle this in the future. In the meantime, you need to do the following:

1. Manually set the usertile on a local account.
2. Open the registry using psexec -s -i regedit.exe
3. Navigate to the usertile that you set
4. Export that key to a reg file
5. Remove all data from the reg file except the “usertile”=HEX:….. entry
6. Specify that reg file instead of the bmp file.

This allows us to leverage the Operating System to do the hard work of creating the registry key and use the script to inject that key in the proper user account.

If you need any further help, feel free to email me directly at micah@xtremeconsulting.com.

14. Micah Rowland - July 13, 2010

The script is in the post Suzzy, I’ve removed the separator to make copy/pasting the code easier. Just drop it into notepad, save as “UserTile.vbs” and you should be ready to go. Feel free to drop an email if you need help.

15. Suzzy Williams - July 8, 2010

awesome post, is it possible to get a copy of the script?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: