Jump to content

UAP - set User Account Picture


t0nZ
 Share

Recommended Posts

Automate the change of the (current) user account picture was a script that I was missing, but I was sure to find an easy command line, like a simple powershell.

It was not the case, I found only methods to change it in Active Directory environments, and my goal was to change the account picture also in freshly installed win 10/11 non domain OSes, and it works also in domain machines.

The focus is to take care of every aspect of the process.

The code:

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Icon=Icone\angryRabbit.ico
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
;UAP
;User Account Picture
;()NSC 2023

;notes
; only way to access 64-bit node in a 32-bit compiled script is using HKLM64
;Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AccountPicture\Users

;main inspiration
;https://social.msdn.microsoft.com/Forums/sqlserver/en-US/3f50b25a-4c47-4bc8-959e-1108419288a7/how-to-set-specific-users-account-picture-from-cmd?forum=scripting

;PSexec to use as System '-s' switch
;https://learn.microsoft.com/it-it/sysinternals/downloads/psexec

;image processing
;https://imagemagick.org/index.php
;https://imagemagick.org/script/command-line-processing.php
;needed VCredist
;https://learn.microsoft.com/it-it/cpp/windows/latest-supported-vc-redist?view=msvc-170
;https://aka.ms/vs/17/release/vc_redist.x64.exe

;permissions SETACL
;https://helgeklein.com/setacl/
;https://www.autoitscript.com/forum/topic/162656-change-registry-key-permissions/?do=findComment&comment=1182956

;interesting but not used (AD related)
;https://woshub.com/how-to-set-windows-user-account-picture-from-active-directory/

#RequireAdmin

#include <Security.au3>
#include <Array.au3>
#include <File.au3>
#include <String.au3>
#include <_Gollog.au3>

$mygui = "UAP   -   NSC 2023"
gollog_ini($mygui, 600, 300, 200, 200, "0x050980", "0xf2dcc3", "consolas") ;f2dcc3

Global $tempfolder = "c:\temp", $ver = "V.1.0"
Global $onlyexe4conversion = "magick.exe", $exe4conversion = $tempfolder & "\" & $onlyexe4conversion
Global $onlyexe4regperm = "setacl.exe", $exe4regperm = $tempfolder & "\" & $onlyexe4regperm
Global $onlyexe4PsExec = "psexec64.exe", $exe4PsExec = $tempfolder & "\" & $onlyexe4PsExec
Global $vcredist = $tempfolder & "\" & "VC_redist.x64.exe"

Global $registrypath = "hklm64\SOFTWARE\Microsoft\Windows\CurrentVersion\AccountPicture\Users\"

Global $registrypath4SETACL = "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AccountPicture\Users\"

Global $aSizes[11] = ["32x32", "40x40", "48x48", "64x64", "96x96", "192x192", "208x208", "240x240", "424x424", "448x448", "1080x1080"]

Gollog("START UAP " & $ver)

Global $sConsoleUser = _GetConsoleUser()
Gollog("Console User: " & $sConsoleUser & @CRLF)

Global $aUserProfile = _GetProfile($sConsoleUser)
Gollog("Console User Path: " & $aUserProfile[1][1] & @CRLF)
Global $folder4accountPictures = $aUserProfile[1][1] & "\" & "UAP"
;_ArrayDisplay($aUserProfile, "current USER")

Global $aexpprof = _StringExplode($aUserProfile[1][2], "\")

Global $CUreg = $aexpprof[UBound($aexpprof) - 1]
Gollog("current user SID: " & $CUreg & @CRLF)

;TEST all users
Gollog("enumerating all users:")
Global $allUserProfiles = _GetProfile()
For $i = 1 To UBound($allUserProfiles) - 1
    Gollog($allUserProfiles[$i][0] & " " & $allUserProfiles[$i][2])
Next
;_ArrayDisplay($allUserProfiles, "all USERS")

createFolder4userPictures()

TempCreate()

SetRegPermissions()

VcRedist()

GenPicS()

Gollog("END UAP")

Func GenPicS()

    ;image prepare
    Gollog("picking image for avatar")
    Local $sourceimg = FileOpenDialog("Select Image for your User Profile", @UserProfileDir & "\" & "downloads", "Images (*.png;*.jpg;*.jpeg;*.gif;*.bmp)", 1)
    Local $sDrive = "", $sDir = "", $sFileName = "", $sExtension = ""
    Local $aPathSplit = _PathSplit($sourceimg, $sDrive, $sDir, $sFileName, $sExtension)

    ;registry prepare
    If RegDelete($registrypath & $CUreg) Then
        Gollog("regdelete ok " & @CRLF)
        Gollog($registrypath & $CUreg & @CRLF)
    Else
        Gollog("regdelete error " & @error & @CRLF)
        Gollog($registrypath & $CUreg & @CRLF)
    EndIf

    If RegWrite($registrypath & $CUreg) Then
        Gollog("regwrite root ok " & @CRLF)
    Else
        Gollog("regwrite root error " & @error & @CRLF)
    EndIf

    For $i = 0 To UBound($aSizes) - 1
        ;image conversion

        ShellExecuteWait($exe4conversion, $sourceimg & " -resize " & $aSizes[$i] & " " & $folder4accountPictures & "\" & $sFileName & "_" & $aSizes[$i] & $sExtension)

        ;registry write
        Local $apartsize = _StringExplode($aSizes[$i], "x")
        If RegWrite($registrypath & $CUreg, "Image" & $apartsize[0], "REG_SZ", $folder4accountPictures & "\" & $sFileName & "_" & $aSizes[$i] & $sExtension) Then
            Gollog("regewrite image ok " & $aSizes[$i] & @CRLF)
            Gollog($registrypath & $CUreg & "  Image" & $apartsize[0] & "  REG_SZ  " & $folder4accountPictures & "\" & $sFileName & "_" & $aSizes[$i] & $sExtension)
        Else
            Gollog("regwrite image error " & @error & @CRLF)
            Gollog($registrypath & $CUreg & "  Image" & $apartsize[0] & "  REG_SZ  " & $folder4accountPictures & "\" & $sFileName & "_" & $aSizes[$i] & $sExtension)
        EndIf
    Next
EndFunc   ;==>GenPicS

Func VcRedist()
    Gollog("installing Microsoft Visual C++ Redistributable ")
    ShellExecuteWait($vcredist, "/install /passive /norestart")
EndFunc   ;==>VcRedist

Func SetRegPermissions()
    Gollog("creating file to set registry permissions")
    ;writing bat file
    Local $batfile = $tempfolder & "\" & "UAPsetacl.bat"
    If FileExists($batfile) Then
        FileDelete($batfile)
        gollog("deleted old " & $batfile)
    EndIf

    Local $hFileOpen = FileOpen($batfile, $FO_APPEND)
    If $hFileOpen = -1 Then
        MsgBox($MB_SYSTEMMODAL, "", "An error occurred whilst writing the UAPsetacl.bat file", 3)
        GOLLOG("An error occurred whilst writing the UAPsetacl.bat file")
        Exit
    EndIf

    FileWriteLine($hFileOpen, 'c:\temp\setacl.exe -on "' & $registrypath4SETACL & $CUreg & '" -ot reg -actn ace -ace "n:Administrators;p:full"')

    FileClose($hFileOpen)
    Gollog("set registry permissions")
    ;launch permission set on registry
    ShellExecuteWait($exe4PsExec, " -s -accepteula " & $batfile)

EndFunc   ;==>SetRegPermissions

Func TempCreate()
    Gollog("deploying necessary temp files")
    If Not FileExists($tempfolder) Then DirCreate($tempfolder)

    FileInstall("C:\nsc_TEST\resources\imagemagick\magick.exe", $exe4conversion, 1)

    FileInstall("C:\nsc_TEST\resources\SetACL\64 bit\setacl.exe", $exe4regperm, 1)

    FileInstall("C:\NSC_test\resources\PSExec\PsExec64.exe", $exe4PsExec, 1)

    FileInstall("C:\nsc_TEST\resources\VCredist\VC_redist.x64.exe", $vcredist, 1)

EndFunc   ;==>TempCreate

;Create folder for user pictures - cleaning pre existing
Func createFolder4userPictures()
    Gollog("creating folder for new avatar pictures")
    If FileExists($folder4accountPictures & "\") Then
        DirRemove($folder4accountPictures, 1)
    EndIf

    DirCreate($folder4accountPictures)
EndFunc   ;==>createFolder4userPictures

;Use WMI query to get user logged onto console
Func _GetConsoleUser()
    Local $objWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!//.")
    Local $colUsers, $objUser
    Local $strAccount

    $colUsers = $objWMIService.InstancesOf("Win32_ComputerSystem")
    For $objUser In $colUsers
        $strAccount = $objUser.UserName
    Next

    If $strAccount <> "" Then
        Return $strAccount
    Else
        Return (1)
    EndIf
EndFunc   ;==>_GetConsoleUser

; #FUNCTION# ====================================================================================================================================
; Name...........: _GetProfile
; Description ...: Determine each user's Profile folder, the user's SID and if the profile is loaded to the registry
; Syntax.........: _GetProfile([$sAccount, $sComputer])
; Parameters ....: $sAccount - User account name, defaults to all users
;                  $sComputer - Computer name, the local computer is default
; Requirement(s).: Service 'RemoteRegistry' running on the target computer
;                  When the target computer is the local computer, the 'RemoteRegistry' service isn't required
; Return values .: An array containing the path to each user's profile folder, the user's SID
;                  The array returned is two-dimensional and is made up as follows:
;                  $array[0][0] = Number of profiles
;                  $array[1][0] = 1st user name
;                  $array[1][1] = Path to 1st user profile
;                  $array[1][2] = 1st user registry hive
;                  $array[1][3] = 1 if 1st user profile is loaded to the registry, 0 if not
;                  $array[2][0] = 2nd user name
;                  $array[2][1] = Path to 2nd user profile
;                  $array[2][2] = 2nd user registry hive
;                  $array[2][3] = 1 if 2nd user profile is loaded to the registry, 0 if not
;                  ...
;                  $array[n][0] = nth user name
;                  $array[n][1] = Path to nth user profile
;                  $array[n][2] = nth user registry hive
;                  $array[n][3] = 1 if nth user profile is loaded to the registry, 0 if not
; Author ........: engine
; Modified.......: AdamUL
; Remarks .......:
; Related .......:
; Link ..........;
; Example .......; _GetProfile("Default User") to get Default User's profile data on the local computer
; ===============================================================================================================================================

Func _GetProfile($sAccount = "", $sComputer = @ComputerName)
    Local $avArray[1][4], $sDefaultUser, $sEnv
    Local Const $sProfileListKey = "\\" & $sComputer & "\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
    Local Const $sRootKey = "\\" & $sComputer & "\HKEY_USERS\"
    Local Const $sDefaultUser1 = RegRead($sProfileListKey, "DefaultUserProfile")
    Local Const $iDefaultUser1Error = @error
    Local Const $sDefaultUser2 = RegRead($sProfileListKey, "Default")
    Local Const $iDefaultUser2Error = @error
    If $iDefaultUser1Error And $iDefaultUser2Error Then
        $avArray[0][0] = 0
        Return $avArray
    EndIf
    If $iDefaultUser1Error Then
        $sDefaultUser = "Default"
    Else
        $sDefaultUser = $sDefaultUser1
    EndIf

    If $sAccount = "" Or $sAccount = $sDefaultUser Then
        Local $iInstance, $sSID
        While 1
            $iInstance += 1
            $sSID = RegEnumKey($sProfileListKey, $iInstance)
            If @error Then ExitLoop
            If StringLen($sSID) > 8 Then ProfileAdd($avArray, $sSID, $sProfileListKey, $sRootKey)
        WEnd
        Local $u = UBound($avArray), $iSum
        For $k = 1 To $u - 1
            $iSum += $avArray[$k][3]
        Next
        ReDim $avArray[$u + 1][4]
        $avArray[$u][0] = $sDefaultUser
        $avArray[$u][1] = RegRead($sProfileListKey, "ProfilesDirectory") & "\" & $sDefaultUser
        If $iSum = 0 Then
            $avArray[$u][2] = "\\" & $sComputer & "\HKEY_CURRENT_USER"
            $avArray[$u][3] = 1
        Else
            Local $avDomain, $iN = 998, $sDSID, $avDU
            $avDomain = _Security__LookupAccountName($sComputer, $sComputer)
            Do
                $iN += 1
                $sDSID = $avDomain[0] & "-" & $iN
                $avDU = _Security__LookupAccountSid($sDSID, $sComputer)
            Until $avDU = 0
            $avArray[$u][2] = $sRootKey & $sDSID
            $avArray[$u][3] = 0
        EndIf
        If $sAccount = $sDefaultUser Then
            Local $avNew[2][4] = [["", "", "", ""], [$avArray[$u][0], $avArray[$u][1], $avArray[$u][2], $avArray[$u][3]]]
            $avArray = $avNew
        EndIf
    Else
        Local $avSID = _Security__LookupAccountName($sAccount, $sComputer)
        If $avSID = 0 Then
            $avArray[0][0] = 0
            Return $avArray
        Else
            ProfileAdd($avArray, $avSID[0], $sProfileListKey, $sRootKey)
        EndIf
    EndIf
    $avArray[0][0] = UBound($avArray) - 1
    For $j = 1 To $avArray[0][0]
        $sEnv = StringRegExp($avArray[$j][1], "\x25\S{1,128}\x25", 1)
        If Not @error Then $avArray[$j][1] = StringReplace($avArray[$j][1], $sEnv[0], EnvGet(StringReplace($sEnv[0], "%", "")))
    Next
    Return $avArray
EndFunc   ;==>_GetProfile

; #INTERNAL_USE_ONLY#============================================================================================================================
; Name...........: ProfileAdd
; Description ...: Add profile data to an array that will be returned by _GetProfile function
; Syntax.........: ProfileAdd($avArray, $sSID, $sProfileListKey, $sRootKey)
; Parameters ....: $avArray - Array
;                  $sSID - Account SID
;                  $sProfileListKey - Constant defined inside _GetProfile function
;                  $sRootKey - Constant defined inside _GetProfile function
; Requirement(s).:
; Return values .:
; Author ........: engine
; Modified.......: AdamUL
; Remarks .......: For internal use only
; Related .......:
; Link ..........;
; Example .......;
; ===============================================================================================================================================

Func ProfileAdd(ByRef $avArray, $sSID, Const $sProfileListKey, Const $sRootKey)
    Local $sPath, $i
    Local $asSplit = Split_sKey("\" & $sProfileListKey)
    Local $avUser = _Security__LookupAccountSid($sSID, $asSplit[0])
    If Not @error And $avUser <> 0 Then
        If $avUser[2] = 1 Then
            $sPath = RegRead($sProfileListKey & "\" & $sSID, "ProfileImagePath")
            If Not @error Then
                $i = UBound($avArray)
                ReDim $avArray[$i + 1][4]
                $avArray[$i][0] = $avUser[0]
                $avArray[$i][1] = $sPath
                $avArray[$i][2] = $sRootKey & $sSID
                RegEnumKey($sRootKey & $sSID, 1)
                If @error Then
                    $avArray[$i][3] = 0
                Else
                    $avArray[$i][3] = 1
                EndIf
            EndIf
        EndIf
    EndIf
EndFunc   ;==>ProfileAdd

; #INTERNAL_USE_ONLY#============================================================================================================================
; Name...........: Split_sKey
; Description ...: Splits $sKey between computername, username and keyname
; Syntax.........: Split_sKey($sKey)
; Parameters ....: $sKey - Reg function main key
; Requirement(s).:
; Return values .:
; Author ........: engine
; Modified.......:
; Remarks .......: For internal use only
; Related .......:
; Link ..........;
; Example .......;
; ===============================================================================================================================================

Func Split_sKey($sKey)
    Local $asArray[3]
    If StringInStr($sKey, "\\\") = 1 Then
        Local $asComputer = StringRegExp($sKey, "\\\\\\[^\\]*\\", 1)
        If Not @error Then
            $asArray[0] = StringTrimRight(StringTrimLeft($asComputer[0], 3), 1)
            $sKey = StringReplace($sKey, $asComputer[0], "\", 1)
            If Not StringInStr($sKey, "\\") = 1 Then $sKey = StringTrimLeft($sKey, 1)
        EndIf
    EndIf
    If $asArray[0] = "" Then $asArray[0] = @ComputerName
    If StringInStr($sKey, "\\") = 1 And Not StringInStr($sKey, "\\\") = 1 Then
        Local $asUser = StringRegExp($sKey, "\\\\[^\\]*\\", 1)
        If Not @error Then
            $asArray[1] = StringTrimRight(StringTrimLeft($asUser[0], 2), 1)
            $sKey = StringReplace($sKey, $asUser[0], "", 1)
        EndIf
    EndIf
    If Not (StringInStr($sKey, "\") = 1 Or StringInStr($sKey, "\", 0, -1) = StringLen($sKey) Or StringInStr($sKey, "\\")) Then
        $asArray[2] = $sKey
    EndIf
    Return $asArray
EndFunc   ;==>Split_sKey

3+1 external executables are needed, look at notes at the top of che code.

The _gollog udf is here.

The abstract is this  :

  1. it determines the current user SID
  2. it gives control to the admin user to the right registry entries, because they are changeable only by "system" account")
  3. it creates the needed pictures and write down to the registry the correct keys.

Research on these topics was made in this thread.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...