jump to navigation

UserTile Automation June 23, 2010

Posted by Micah Rowland in USMT, VBscript, Windows 7.
16 comments

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!

Introduction June 23, 2010

Posted by Micah Rowland in Uncategorized.
3 comments

My name is Micah Rowland and I have been added to the Xtreme Deployment consulting group as an expert in automation and reverse engineering. As we all know, some of the tasks that customers ask of us to accomplish during the MDT process are very simple to do manually, but in the background, are tricky to nail down when we go to automate the same processes.  In my posts, I hope to reveal undocumented aspects to the Windows operating system and show how to develop automation around these processes. Let’s get started!

Follow

Get every new post delivered to your Inbox.

Join 84 other followers