PowerShell - Setting Default Wallpaper

Setting the default wallpaper in Windows 10 using Powershell seems pretty straightforward at first, but there are a few roadblocks to look out for.

Say you manage a large collection of machines, and need to set different wallpapers based on location, department, or even monitor size? Sure you could use GPO, setting different wallpapers for different OUs, but what if you want different wallpapers on machines in the same OU? In my experience, you can achieve much more granular control using powershell.

For instance, the following helper function will return true if the machine’s wallpaper is “wide”, which we define as having a width that is 2.2 or more times the height.

Detecting Wide-Screen Monitors

function wide_screen? {
    $vid_con = (Get-WmiObject -Class Win32_VideoController)
    $screen_info = $vid_con.VideoModeDescription -split "[^0-9]+" 
	$screen_width, $screen_height = $screen_info | Select-Object -First 2
	$screen_ratio = $screen_width / $screen_height
	return $screen_ratio -ge 2.2
}

In my own collection machines have names containing their department and location info, and we save all that data in environment variables. Using these variables we determine exactly which wallpaper path to use. I say path because Windows 10 is tricky with the default wallpaper. If the monitor is a non-standard (1080p) resolution, then good old img0.jpg is not the default wallpaper. Instead an image most closely matching the resolution of the monitor in ‘C:\Windows\Web\4k\Wallpaper\Windows’ will be used.

To ensure your custom wallpaper displays well on all monitors, you should generate versions of it at these named resolutions:

  1. img0_768x1024.jpg
  2. img0_768x1366.jpg
  3. img0_1024x768.jpg
  4. img0_1200x1920.jpg
  5. img0_1366x768.jpg
  6. img0_1600x2560.jpg
  7. img0_2160x3840.jpg
  8. img0_2560x1600.jpg
  9. img0_3840x2160.jpg

Then place those in a folder called ‘4k’ alongside your 1080p img0.jpg. The path to this collection of wallpapers (hopefully on a server!) is what we’ll give to the following function:

Setting the wallpaper

function set_wallpaper($new_wallpaper_path) {
	$sysdata = 'C:\ProgramData\Microsoft\Windows\SystemData'
	$old_wallpaper = 'C:\Windows\Web\Wallpaper\Windows\img0.jpg'
	$old_4k = 'C:\Windows\Web\4k\Wallpaper\Windows'

	$new_wallpaper = "$new_wallpaper_path\img0.jpg"
	$new_4k = "$new_wallpaper_path\4k"

	if ((Get-FileHash $new_wallpaper).hash  -ne (Get-FileHash $old_wallpaper).hash) {
		"`n  Removing old wallpaper..."
		get_ownership $sysdata
		make_old $sysdata
		make_old $old_wallpaper
		make_old $old_4k

		"`n  Setting wallpaper to $new_wallpaper..."
		Copy-Item $new_wallpaper $old_wallpaper -Force
		if (Test-Path $new_4k) { robocopy $new_4k $old_4k /E /XO /R:3 }
		"`n  Done!"
	} else {
		"`n  $new_wallpaper is already set as our wallpaper"
	}
}

So what is this doing? Let’s break it down.

First off, I save variables for various system paths. Some of these we only use once, but saving them to a variable helps keep the code shorter and more readable.

The ‘if’ statement checks to see if the current 1080p wallpaper is the same file as the new one we are trying to set, using hash. If the hashes match we go to the else statement and simply log that nothing was changed.

If the hashes don’t match, we must be setting a new wallpaper. Here many encounter another roadblock in the form of SystemData. Systemdata, located at ‘C:\ProgramData\Microsoft\Windows\SystemData’, contains a cached version of the current wallpaper. This cached version is used on the lockscreen of the machine, and if you set a new wallpaper without also clearing SystemData, you’ll have the old wallpaper on the lockscreen still!

To clear out SystemData I first call a helper to take ownership of it and grant full permissions for Administrators.

Get Ownership

function get_ownership($dir_path) {
	$dir_owner = (Get-Item $dir_path).GetAccessControl().Owner
	if (!($dir_owner -like "*Administrators")) { takeown /f $dir_path /a /r /d y }
	icacls $dir_path /q /t /c /grant 'Administrators:(OI)(CI)F'
	"`n  Granted rights on $dir_path"
}

Once that’s taken care of it’s simply a matter of renaming the old sysdata and wallpaper files to make way for the new ones. These ‘backups’ will only be as old as the last time you changed the wallpaper though, so it’s a good idea to save the default windows wallpapers before you set it to a custom one in the first place!

The regex used in my -replace statement is simply placing ‘_old’ at the end of the file name while retaining the file extension after. It’s a neat trick!

Save Old Version

function make_old($path_in) {
	if ((Get-Item $path_in) -is [System.IO.DirectoryInfo]) {
		$new_path = "$($path_in)_old"
		if (!(Test-Path $new_path)) { New-Item $new_path -type directory }
		Move-Item -Path "$path_in\*" -Destination $new_path -Force
	} else {
		$new_path = $path_in -replace '\.(...)$', '_old.$1'
		Move-Item -Path $path_in -Destination $new_path -Force
	}
}

I hope you found this article useful. If you encounter any problems with the code above or if it helped make your life a little easier, hit the ‘@’ symbol above and let me know!

Back