Published in Articles on by Michiel van Oosterhout ~ 3 min read

Windows 11 stores account pictures in a special shell folder with Known Folder ID 0482af6c-08f1-4c34-8c90-e17ec98b1e17. The actual path to this folder is %PUBLIC%\AccountPictures, but the display name is Public Account Pictures.

The special shell folder for account pictures in Windows 11
The special shell folder for account pictures in Windows 11

This folder is in the %PUBLIC% folder, and as account pictures can only be changed by scripts running as NT AUTHORITY\SYSTEM. In our previous article we installed the Invoke-CommandAs PowerShell module so we could use the WMI-to-MDM bridge. If you did not install the module yet, run this command to install it: Install-Module -Name Invoke-CommandAs (this command requires an elevated Windows PowerShell session).

The Windows PowerShell script below downloads a photo and sets it as the account picture for the current user. The photo should have the same width and height, and ideally is at least 1080 pixels wide.

(This script requires an elevated Windows PowerShell session.)

$script = {
    param(
        [Parameter()]
        [string]$sid,
        [Parameter()]
        [string]$url
    )

    # Generate a unique prefix
    $prefix = [guid]::NewGuid().ToString("B").ToUpper()

    # Download a profile photo
    $downloadFilePath = "$Env:TEMP\$prefix.jpg"
    Invoke-WebRequest -Uri $url -OutFile $downloadFilePath

    # Load the photo into memory
    Add-Type -AssemblyName "System.Drawing"
    $photo = [System.Drawing.Image]::FromFile($downloadFilePath)

    # Create the quality parameter we will use for resizing the photo
    $encoder = [System.Drawing.Imaging.Encoder]::Quality
    $quality = 90
    $parameters = [System.Drawing.Imaging.EncoderParameters]::new()
    $parameters.Param[0] = [System.Drawing.Imaging.EncoderParameter]::new($encoder, $quality)

    # Get an image encoder for photos
    $codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object { 
        $_.MimeType -eq "image/jpeg"
    }

    # This is the path to a folder inside the special account pictures folder
    $destinationDirectoryPath = "$Env:PUBLIC\AccountPictures\$sid"
    if (Test-Path $destinationDirectoryPath)
    {
        Remove-Item -Force -Path $destinationDirectoryPath -Recurse
    }
    $directory = New-Item -ItemType Directory -Path $destinationDirectoryPath

    # This is the path in the Windows Registry
    $keyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AccountPicture\Users\$sid"
    if (-not (Test-Path $keyPath))
    {
        $key = New-Item -ItemType Directory -Path $keyPath
    }

    # Create a new file for each size
    foreach ($size in $(32, 40, 48, 64, 96, 192, 208, 240, 424, 448, 1080))
    {
        # Draw the original photo onto a bitmap
        $bitmap = [System.Drawing.Bitmap]::new($size, $size)
        $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
        $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic

        $graphics.Clear([System.Drawing.Color]::White)
        $graphics.DrawImage($photo, 0, 0, $size, $size)

        # Save the bitmap
        $destinationFilePath = "$destinationDirectoryPath\$prefix-Image$size.jpg"
        $bitmap.Save($destinationFilePath, $codec, $parameters)
        $bitmap.Dispose()

        # Register the saved file in the Windows Registry
        $value = New-ItemProperty -Force `
            -Name "Image$size" `
            -Path $keyPath `
            -PropertyType "String" `
            -Value $destinationFilePath
    }

    # Dispose the photo
    $photo.Dispose()

    # Delete the downloaded file
    Remove-Item -Force -Path $downloadFilePath
}

# Get current user's SID
$user = Get-LocalUser -Name $Env:USERNAME
$sid = $user.SID.Value

# Get the URL of a photo to use as the account picture
$url = "https://michielvoo.net/michiel-van-oosterhout.jpg"

# Run the script block above as SYSTEM, passing in the SID
Invoke-CommandAs -AsSystem -ScriptBlock $script -ArgumentList $sid,$url

The script block has access to the account pictures folder because it is running as NT AUTHORITY\SYSTEM. The script block downloads a photo to C:\Windows\Temp, saves a set of resized photos in the account pictures folder (after removing the current set), and then updates the corresponding values in the Windows Registry.

A restart is required for the changes to take effect:

Windows 11 account picture as shown in the Settings app
Windows 11 account picture as shown in the Settings app

Summary

Although you may think of your profile picture as something that you should be able to change, it is actually the system account that is allowed to change it. This is because the files are in %PUBLIC%, because they should be visible on the logon screen. Using the 3rd-party cmdlet Invoke-CommandAs we are able to run a script block with the required permissions to set our account picture.