Skip to content
No results
  • Realm Labs Home
  • Projects
    • Homelab
    • Home Automation
    • Networking
    • AI
  • Guides
    • Windows
    • Linux
    • Raspberry Pi
    • Networking
    • Home Automation
    • Homelab
    • Self Hosting
  • About Realm Labs
  • IT Services
  • Lab
    • Lab Notes
      • Cloudflare
      • Docker
      • Domain / ADCU
      • Home Assistant
      • Monitoring
      • Node-Red
      • Proxmox
      • Synology
    • Hardware
  • Realm Labs Home
  • Projects
    • Homelab
    • Home Automation
    • Networking
    • AI
  • Guides
    • Windows
    • Linux
    • Raspberry Pi
    • Networking
    • Home Automation
    • Homelab
    • Self Hosting
  • About Realm Labs
  • IT Services
  • Lab
    • Lab Notes
      • Cloudflare
      • Docker
      • Domain / ADCU
      • Home Assistant
      • Monitoring
      • Node-Red
      • Proxmox
      • Synology
    • Hardware

Deploying Custom Domain User Profile Pictures with Group Policy

  • Paul HucklePaul Huckle
  • 26 August 2026
  • Domain / ADCU, Windows

I wanted domain users on my order.realm network to have their own Windows account pictures automatically applied when they log on to a domain computer.

The environment in this example uses:

  • Active Directory domain: order.realm
  • Domain controller / file server: Seido
  • Group Policy Management
  • Windows domain-joined PCs
  • A central PowerShell script stored in SYSVOL
  • A scheduled task deployed through Group Policy
  • Different profile pictures based on the logged-on username

The final result is that a user can log on to any computer covered by the GPO and Windows automatically generates and applies the correct account picture.


The final layout

The files are stored here:

\\Seido\SYSVOL\order.realm\scripts\

The main script is:

\\Seido\SYSVOL\order.realm\scripts\Set-AccountPicture.ps1

Profile images are stored beneath it:

\\Seido\SYSVOL\order.realm\scripts\AccountPictures\

For example:

AccountPictures
├── TuroTgV1.png
├── johnny_cage.png
├── mileena.png
├── kitana.png
├── scorpion.png
├── coin_ops.png
└── raiden.png

The scheduled task deployed through Group Policy calls the PowerShell script directly from SYSVOL.

The exported task confirmed that the working configuration runs as NT AUTHORITY\SYSTEM, uses the highest available privileges, has an At log on trigger with a 30-second delay, and launches PowerShell from the direct \\Seido\SYSVOL... path.


Why a scheduled task is needed

Windows account pictures are registered under:

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AccountPicture\Users\<SID>

Because this is under HKLM, an ordinary domain user normally cannot write the required values.

The solution was therefore to run the script as:

NT AUTHORITY\SYSTEM

with:

Run with highest privileges

The task runs when somebody logs on.

This lets the script modify the local computer while still identifying which interactive user has just logged on.


Group Policy layout

I created a dedicated GPO called:

OrderRealm Account Picture

Rather than mixing this with my Folder Redirection and Drive Maps policy, the account-picture policy is kept separate.

The GPO is linked to:

order.realm
└── NetherRealm
    └── Computers

This is important.

Because the scheduled task is under Computer Configuration, the GPO must apply to the computer account, not simply the user account.

For example, one of my computers is:

CN=RACING-RIG,
OU=Computers,
OU=NetherRealm,
DC=order,
DC=realm

Once the GPO was correctly linked to the Computers OU, this appeared in:

gpresult /r /scope computer

under:

Applied Group Policy Objects
-----------------------------
Computer Configs
Apps
OrderRealm Account Picture
Default Domain Policy

That was the key confirmation that the computer was finally receiving the policy.


Creating the scheduled task

Edit:

OrderRealm Account Picture

and go to:

Computer Configuration
→ Preferences
→ Control Panel Settings
→ Scheduled Tasks

Create:

New
→ Scheduled Task (At least Windows 7)

General

Use:

Name:
Set OrderRealm Account Picture

User account:
NT AUTHORITY\SYSTEM

Enable:

Run whether user is logged on or not
Run with highest privileges

The working exported task uses SYSTEM with the highest run level.


Trigger

Create an:

At log on

trigger.

Use:

Any user

and add:

Delay task for: 30 seconds

The delay became useful because the script needs an interactive Windows session to exist before it can determine which user has logged on. The exported working task contains a PT30S delay.


Action

Program:

powershell.exe

Arguments:

-ExecutionPolicy Bypass -File "\\Seido\SYSVOL\order.realm\scripts\Set-AccountPicture.ps1"

I originally used the domain DFS-style path:

\\order.realm\SYSVOL\order.realm\scripts\

but in this environment the direct server path proved more reliable:

\\Seido\SYSVOL\order.realm\scripts\

The final exported task uses this direct path.


Detecting the logged-on user

One of the more interesting problems was identifying the user while the script itself was running as SYSTEM.

Initially I used:

(Get-CimInstance Win32_ComputerSystem).UserName

This worked on one machine but returned nothing on another.

The log showed:

Running as: NT AUTHORITY\SYSTEM
ERROR: No logged-on user found.

The more reliable method was to find explorer.exe and ask Windows who owns that process.

Because Explorer normally belongs to the interactive desktop user, this provides the actual logged-on domain account.

The script waits for Explorer to appear and retries for up to 60 seconds.


Mapping users to pictures

The script can use one central configuration to decide which picture belongs to which user.

For example:

$PictureFile = switch ($Username.ToLower()) {

    "turokhan" { "TuroTgV1.png" }

    "luke"     { "johnny_cage.png" }

    "amanda"   { "mileena.png" }

    "emma"     { "kitana.png" }

    "scorpion" { "scorpion.png" }

    "lamb"     { "coin_ops.png" }

    default    { $null }
}

That means every computer can run exactly the same script.

The script simply determines who is logged on and selects the matching image.

To add another user later, I only need to:

  1. Put their PNG in AccountPictures.
  2. Add one line to the switch statement.

For example:

"raiden" { "raiden.png" }

Why the script generates several JPEG files

Simply pointing every Windows account-picture registry value at the original PNG initially resulted in the image not appearing correctly.

Looking at Windows’ existing account-picture registry structure showed that Windows normally keeps several different image sizes.

The working solution therefore generates:

Image32.jpg
Image40.jpg
Image48.jpg
Image64.jpg
Image96.jpg
Image192.jpg
Image208.jpg
Image240.jpg
Image424.jpg
Image448.jpg
Image1080.jpg

These are stored under:

C:\Users\Public\AccountPictures\<USER-SID>\

For example:

C:\Users\Public\AccountPictures\
S-1-5-21-1646595580-629131343-3901383525-1107\

The registry then points each Windows image size to the corresponding JPEG.

For example:

Image32
→ C:\Users\Public\AccountPictures\<SID>\Image32.jpg

Image96
→ C:\Users\Public\AccountPictures\<SID>\Image96.jpg

Image448
→ C:\Users\Public\AccountPictures\<SID>\Image448.jpg

Image1080
→ C:\Users\Public\AccountPictures\<SID>\Image1080.jpg

Once this structure was used, the account picture displayed correctly.


Final PowerShell script

The following is the structure of the working multi-user script.

$LogFolder = "C:\ProgramData\OrderRealm"
$LogFile   = "$LogFolder\SetAccountPicture.log"

New-Item -Path $LogFolder -ItemType Directory -Force | Out-Null

try {

    "----- $(Get-Date) -----" | Out-File $LogFile -Append

    "Running as: $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)" |
        Out-File $LogFile -Append

    # Find the interactive logged-on user through Explorer
    $LoggedOnUser = $null

    for ($i = 0; $i -lt 12; $i++) {

        $Explorer = Get-CimInstance Win32_Process -Filter "Name='explorer.exe'" |
            Select-Object -First 1

        if ($Explorer) {

            $Owner = Invoke-CimMethod `
                -InputObject $Explorer `
                -MethodName GetOwner

            if ($Owner.User) {
                $LoggedOnUser = "$($Owner.Domain)\$($Owner.User)"
                break
            }
        }

        Start-Sleep -Seconds 5
    }

    if (-not $LoggedOnUser) {
        throw "No interactive logged-on user found after 60 seconds."
    }

    "Logged-on user: $LoggedOnUser" |
        Out-File $LogFile -Append

    $Domain, $Username = $LoggedOnUser -split "\\", 2

    # Choose image for user
    $PictureFile = switch ($Username.ToLower()) {

        "turokhan" { "TuroTgV1.png" }
        "luke"     { "johnny_cage.png" }
        "amanda"   { "mileena.png" }
        "emma"     { "kitana.png" }
        "scorpion" { "scorpion.png" }
        "lamb"     { "coin_ops.png" }

        default    { $null }
    }

    if (-not $PictureFile) {
        throw "No account picture configured for user: $Username"
    }

    "Picture selected: $PictureFile" |
        Out-File $LogFile -Append

    # Get SID
    $Account = New-Object System.Security.Principal.NTAccount($LoggedOnUser)

    $SID = $Account.Translate(
        [System.Security.Principal.SecurityIdentifier]
    ).Value

    "SID: $SID" |
        Out-File $LogFile -Append

    # Source image
    $Source = "\\Seido\SYSVOL\order.realm\scripts\AccountPictures\$PictureFile"

    if (!(Test-Path $Source)) {
        throw "Source image not found: $Source"
    }

    # Native Windows account picture folder
    $DestFolder = "C:\Users\Public\AccountPictures\$SID"

    New-Item `
        -Path $DestFolder `
        -ItemType Directory `
        -Force `
        -ErrorAction Stop | Out-Null

    Add-Type -AssemblyName System.Drawing

    $SourceImage = [System.Drawing.Image]::FromFile($Source)

    $Sizes = 32,40,48,64,96,192,208,240,424,448,1080

    $RegPath =
        "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AccountPicture\Users\$SID"

    New-Item `
        -Path $RegPath `
        -Force `
        -ErrorAction Stop | Out-Null

    foreach ($Size in $Sizes) {

        $Dest = Join-Path $DestFolder "Image$Size.jpg"

        $Bitmap =
            New-Object System.Drawing.Bitmap($Size, $Size)

        $Graphics =
            [System.Drawing.Graphics]::FromImage($Bitmap)

        $Graphics.InterpolationMode =
            [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic

        $Graphics.SmoothingMode =
            [System.Drawing.Drawing2D.SmoothingMode]::HighQuality

        $Graphics.PixelOffsetMode =
            [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality

        $Graphics.DrawImage(
            $SourceImage,
            0,
            0,
            $Size,
            $Size
        )

        $Bitmap.Save(
            $Dest,
            [System.Drawing.Imaging.ImageFormat]::Jpeg
        )

        $Graphics.Dispose()
        $Bitmap.Dispose()

        New-ItemProperty `
            -Path $RegPath `
            -Name "Image$Size" `
            -Value $Dest `
            -PropertyType String `
            -Force `
            -ErrorAction Stop | Out-Null
    }

    $SourceImage.Dispose()

    "Account picture images generated successfully." |
        Out-File $LogFile -Append

    "SUCCESS" |
        Out-File $LogFile -Append

    exit 0
}
catch {

    "ERROR: $($_.Exception.Message)" |
        Out-File $LogFile -Append

    exit 10
}

Useful troubleshooting commands

These were the commands that proved most useful while getting this working.

Check whether the GPO applies to the computer

gpresult /r /scope computer

Look for:

OrderRealm Account Picture

under:

Applied Group Policy Objects

If it isn’t there, there is no point troubleshooting the PowerShell script yet.

The computer isn’t receiving the policy.


Force Group Policy processing

gpupdate /force

If Folder Redirection is also configured, Windows may report that some user policy requires a logoff.

That is unrelated to the account-picture script itself.


Check whether the scheduled task exists

Get-ScheduledTask -TaskName "Set OrderRealm Account Picture"

If Windows says no scheduled task was found, check the GPO scope/link before touching the script.


Run the task manually

This became the quickest way to test changes:

Start-ScheduledTask -TaskName "Set OrderRealm Account Picture"

Because the task reads the script directly from:

\\Seido\SYSVOL\order.realm\scripts\

changing the PowerShell script itself does not require a new gpupdate.

Just save the script and run the existing task again.


Check the task result

Get-ScheduledTaskInfo -TaskName "Set OrderRealm Account Picture" |
Select-Object LastRunTime, LastTaskResult

A successful execution should return:

LastTaskResult
0

The script deliberately exits with:

10

when an exception occurs.


Read the diagnostic log

Get-Content "C:\ProgramData\OrderRealm\SetAccountPicture.log" -Tail 20

A successful run looks roughly like:

Running as: NT AUTHORITY\SYSTEM
Logged-on user: ORDERREALM\turokhan
Picture selected: TuroTgV1.png
SID: S-1-5-21-...
Account picture images generated successfully.
SUCCESS

This log was invaluable because a scheduled task can otherwise fail without giving much useful information.


Check generated images

$SID = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value

Get-ChildItem "C:\Users\Public\AccountPictures\$SID"

A working deployment should show all of the generated JPEG sizes.


Check the registry

$SID = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value

reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AccountPicture\Users\$SID"

Each entry should point to the corresponding JPEG under:

C:\Users\Public\AccountPictures\<SID>\

Things that caught me out

There were several useful lessons from this project.

User Configuration vs Computer Configuration

The first scheduled task was placed under User Configuration.

That made the setup confusing because the task itself needed to operate as SYSTEM.

Moving it into a dedicated GPO under:

Computer Configuration

and linking that GPO to the computer OU made the design much easier to understand.


A GPO can look correct but still not be applying

At one stage the GPO link looked correct in Group Policy Management, but:

gpresult /r /scope computer

did not show it.

After forcing:

gpupdate /force

the GPO appeared and the scheduled task was subsequently created.

The lesson is simple:

Trust gpresult on the client more than appearances in GPMC.


SYSTEM is not the logged-on user

When PowerShell runs as SYSTEM:

[System.Security.Principal.WindowsIdentity]::GetCurrent()

returns:

NT AUTHORITY\SYSTEM

It cannot simply be used to determine the domain user.

Using the owner of explorer.exe solved that problem.


Account pictures are more than one image

Copying a PNG wasn’t enough.

Windows expects several account-picture sizes and registry references.

Generating the full JPEG set under:

C:\Users\Public\AccountPictures\<SID>

produced the reliable result.


Use a log file from the beginning

The final script writes to:

C:\ProgramData\OrderRealm\SetAccountPicture.log

That makes troubleshooting dramatically easier.

Instead of wondering whether the task started, which user it detected, which file it selected, or whether registry creation failed, all of those details are recorded.


Final result

The finished arrangement is now centrally managed.

When a supported domain user logs on:

Domain user logs on
        ↓
Computer receives OrderRealm Account Picture GPO
        ↓
Scheduled task waits 30 seconds
        ↓
Task runs PowerShell as SYSTEM
        ↓
Script finds interactive user
        ↓
Username selects matching PNG
        ↓
PNG is read from SYSVOL
        ↓
Windows-size JPEGs are generated locally
        ↓
AccountPicture registry values are created
        ↓
Windows displays the user's custom profile picture

The nice part is that no individual PC needs to know which user belongs to which image.

Everything is controlled from one PowerShell script and one central image directory.

Adding another user later is therefore simply a case of dropping in another image and adding another username-to-picture mapping.

For a home lab this is probably more elaborate than strictly necessary, but it also demonstrates quite nicely how Group Policy Preferences, scheduled tasks, SYSTEM context, Windows SIDs and SYSVOL can all be combined to centrally configure domain PCs.

  • Privacy Policy
  • Contact
  • About Realm Labs

Copyright © 2026 Realm Labs. All rights reserved.

Enjoying Realm Labs?

If you find the projects, guides, or tools on this site useful, consider buying me a coffee! Your support helps keep Realm Labs running and continuously stocked with new build guides and homelab experiments.

Buy Me a Coffee