Windows 10 has been around for a while now, and so have various methods for branding the operating system, like importing custom registry files, executing batch files and so on. If you’ve visited my site before, you should know by now that I’m no fan of using legacy methods (even though they may work just fine, my goal is to find new ways by using modern technologies). A good friend of mine, Jörgen Nilsson, has made an excellent blog post about how to set a custom wallpaper (background) when deploying Windows 10. Jörgens method triggered me to attempt converting it into a PowerShell script instead.
Windows 10 wallpapers
For those of us that have been branding Windows for years now, we’re familiar with the process of replacing img0.jpg in C:\Windows\Web\Wallpaper\Windows. However, with the release of Windows 10, support for 4K and 8K screen resolutions was introduced, in addition to a method that automatically detects the current resolution and attempts to set a wallpaper that matches the width and height. So in Windows 10, we now have two places that we need to configure in order to properly set a custom wallpaper:
- C:\Windows\Web\Wallpaper\Windows
- C:\Windows\Web\4K\Wallpaper\Windows
If you browse the 4K folder, you’ll see that the default Windows 10 wallpaper are present in various resolutions. From my understanding, Windows will attempt to find a suitable wallpaper in this folder that matches the current resolution. If there’s no match, it will default back to the wallpaper in C:\Windows\Web\Wallpaper\Windows. This means, that we also have to replace our custom wallpaper with the defaults in the 4K folder.
Bonus: I’ve not yet tested what would happen if the wallpapers in the 4K folder were simply removed, but my guess is that Windows would default to the img0.jpg in C:\Windows\Web\Wallpaper\Windows.
Script
So in order to set a custom wallpaper in Windows 10, I’ve converted Jörgens method (originally a batch-script) into a PowerShell script with some more logic and error handling. In addition, I’ve also added some logging in a file called SetDefaultWallpaper.log that will be created in C:\Windows\Temp. You’ll find the script below and on my GitHub repository. For the script to properly work, you’ll need to create the following folder structure:
Below is an explanation of the various components required by the Set-DefaultWallpaper.ps1 script for it to work properly:
- 4K folder
- Place your 4K wallpapers in this folder. Make sure that you name them accordingly as the defaults in C:\Windows\Web\4K\Wallpaper\Windows.
- Modules folder
- Download the NTFSSecurity module and extract the contents into this folder: https://ntfssecurity.codeplex.com/
- img0.jpg
- This is the default wallpaper that you want to used when none of the wallpapers in the 4K folder suits the resolution.
If you do not include the NTFSSecurity module in the proper folder, the whole process will break since the PowerShell script relies on this module to take and set ownership of the existing wallpaper files (this is required, and well documented in Jörgens post).
Save the below script as Set-DefaultWallpaper.ps1 and put it in the same folder where you’ve created the required components as described above.
# Functions function Write-LogFile { param( [parameter(Mandatory=$true, HelpMessage="Name of the log file, e.g. 'FileName'. File extension should not be specified")] [ValidateNotNullOrEmpty()] [string]$Name, [parameter(Mandatory=$true, HelpMessage="Value added to the specified log file")] [ValidateNotNullOrEmpty()] [string]$Value, [parameter(Mandatory=$true, HelpMessage="Choose a location where the log file will be created")] [ValidateNotNullOrEmpty()] [ValidateSet("UserTemp","WindowsTemp")] [string]$Location ) # Determine log file location switch ($Location) { "UserTemp" { $LogLocation = ($env:TEMP + "\") } "WindowsTemp" { $LogLocation = ($env:SystemRoot + "\Temp\") } } # Construct log file name and location $LogFile = ($LogLocation + $Name + ".log") # Create log file unless it already exists if (-not(Test-Path -Path $LogFile -PathType Leaf)) { New-Item -Path $LogFile -ItemType File -Force | Out-Null } # Add timestamp to value $Value = (Get-Date).ToShortDateString() + ":" + (Get-Date).ToLongTimeString() + " - " + $Value # Add value to log file Add-Content -Value $Value -LiteralPath $LogFile } # Stage NTFSSecurity module try { $NTFSSecurityModulePath = Join-Path -Path $env:WINDIR -ChildPath "System32\WindowsPowerShell\v1.0\Modules\NTFSSecurity" if (-not(Test-Path -Path $NTFSSecurityModulePath -PathType Container)) { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Staging NTFSSecurity module in: $($NTFSSecurityModulePath)" Copy-Item -Path (Join-Path -Path $PSScriptRoot -ChildPath "Modules") -Destination $NTFSSecurityModulePath -Recurse -ErrorAction Stop } } catch [System.Exception] { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Unable to stage required PowerShell module: NTFSSecurity" ; break } # Import NTFSSecurity module try { Import-Module -Name NTFSSecurity -ErrorAction Stop } catch [System.Exception] { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Unable to import required PowerShell module: NTFSSecurity" ; break } # Scripts variables $NewOwner = "$($env:COMPUTERNAME)\Administrator" $SystemContext = "NT AUTHORITY\SYSTEM" $DefaultWallpaperRootPath = Join-Path -Path $PSScriptRoot -ChildPath "img0.jpg" # Process C:\Windows\WEB\Wallpaper\Windows\img0.jpg try { # Take ownership $DefaultWallpaperImagePath = Join-Path -Path $env:WINDIR -ChildPath "WEB\Wallpaper\Windows\img0.jpg" $CurrentOwner = Get-Item -Path $DefaultWallpaperImagePath | Get-NTFSOwner if ($CurrentOwner.Owner -notlike $NewOwner) { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Setting new owner of '$($NewOwner)' on: $($DefaultWallpaperImagePath)" Set-NTFSOwner -Path $DefaultWallpaperImagePath -Account $NewOwner -ErrorAction Stop } # Grant NT AUTHORITY\SYSTEM and Local Administrator Full Control access try { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Granting '$($SystemContext)' Full Control on: $($DefaultWallpaperImagePath)" Add-NTFSAccess -Path $DefaultWallpaperImagePath -Account $SystemContext -AccessRights FullControl -AccessType Allow -ErrorAction Stop Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Granting '$($NewOwner)' Full Control on: $($DefaultWallpaperImagePath)" Add-NTFSAccess -Path $DefaultWallpaperImagePath -Account $NewOwner -AccessRights FullControl -AccessType Allow -ErrorAction Stop } catch [System.Exception] { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Unable to grant required Full Control permissions on: $($DefaultWallpaperImagePath)" ; break } # Replace wallpaper try { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Replacing default wallpaper in: $($DefaultWallpaperImagePath)" Remove-Item -Path $DefaultWallpaperImagePath -Force -ErrorAction Stop Copy-Item -Path $DefaultWallpaperRootPath -Destination $DefaultWallpaperImagePath -Force -ErrorAction Stop } catch [System.Exception] { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Unable to replace default wallpaper: $($DefaultWallpaperImagePath)" ; break } } catch [System.Exception] { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Unable to take ownership of: $($DefaultWallpaperImagePath)" ; break } # Process C:\Windows\WEB\4K\Wallpaper\Windows recursively $HDWallpaperRoot = Join-Path -Path $PSScriptRoot -ChildPath "4K" $HDWallpapers = Get-ChildItem -Path $HDWallpaperRoot if (($HDWallpapers | Measure-Object).Count -ge 1) { $LocalHDWallpapersPath = Join-Path -Path $env:WINDIR -ChildPath "WEB\4K\Wallpaper\Windows" $LocalHDWallpapers = Get-ChildItem -Path $LocalHDWallpapersPath -Recurse -Filter *.jpg foreach ($LocalHDWallpaper in $LocalHDWallpapers) { # Take ownership $CurrentOwner = Get-Item -Path $LocalHDWallpaper.FullName | Get-NTFSOwner if ($CurrentOwner.Owner -notlike $NewOwner) { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Setting new owner of '$($NewOwner)' on: $($LocalHDWallpaper.FullName)" Set-NTFSOwner -Path $LocalHDWallpaper.FullName -Account $NewOwner -ErrorAction Stop } # Grant NT AUTHORITY\SYSTEM Full Control access try { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Granting '$($SystemContext)' Full Control on: $($LocalHDWallpaper.FullName)" Add-NTFSAccess -Path $LocalHDWallpaper.FullName -Account $SystemContext -AccessRights FullControl -AccessType Allow -ErrorAction Stop Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Granting '$($NewOwner)' Full Control on: $($LocalHDWallpaper.FullName)" Add-NTFSAccess -Path $LocalHDWallpaper.FullName -Account $NewOwner -AccessRights FullControl -AccessType Allow -ErrorAction Stop } catch [System.Exception] { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Unable to grant required Full Control permissions on: $($LocalHDWallpaper.FullName)" ; break } # Remove default wallpaper try { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Removing default wallpaper: $($LocalHDWallpaper.FullName)" Remove-Item -Path $LocalHDWallpaper.FullName -Force -ErrorAction Stop } catch [System.Exception] { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Unable to remove default wallpaper: $($LocalHDWallpaper.FullName)" ; break } } # Copy default wallpapers foreach ($HDWallpaper in $HDWallpapers) { try { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Copying '$($HDWallpaper.FullName)' wallpaper to: $($LocalHDWallpapersPath)" Copy-Item -Path $HDWallpaper.FullName -Destination $LocalHDWallpapersPath -Force -ErrorAction Stop } catch [System.Exception] { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Unable to copy default wallpaper '$($HDWallpaper.FullName)' to: $($LocalHDWallpapersPath)" ; break } } } else { Write-LogFile -Name "SetDefaultWallpaper" -Location WindowsTemp -Value "Unable to locate wallpapers in 4K root folder when processing, skipping the 4K wallpapers" }
Using the script in ConfigMgr
With the above described folder structure in place and the script saved, copy it to a folder in your Content Library in ConfigMgr. The process described below is pretty straight forward and consists of creating a Package in ConfigMgr with the source content prepared earlier in this post and finally adding a Run PowerShell Script step to your Windows 10 task sequence at some point after the Setup ConfigMgr and Windows step.
I’ll not cover the steps of creating a Package, as that should be something everyone is familiar with (if not, you have some reading to do). Simply create a regular Package with no Program, and point the content source to the prepared folder with all the contents. Edit your Windows 10 task sequence and add a step like shown below, where you point to your Package and simply just call the PowerShell script Set-DefaultWallpaper.ps1:
Remember to set the PowerShell execution policy to Bypass, or the script will not execute properly. Below is a snippet from how the SetDefaultWallpaper.log file will look after a successful deployment:
Once you deploy Windows 10 from now on, you’ll get your custom wallpaper instead of the default one.
Will this work to update the information post in-place upgrade?
This script is working for me in 1709. However, it is stretching the JPG across the whole screen. Is there a way to center it like it would if I used policy to set it? My client doesn’t want it required. They want to set a default but allow users to set if they like.
Hello. Has there been an update to this issue?
Hi Rick,
Not yet unfortunately. Please share any findings you may have come across 🙂
Regards,
Nickolaj
Here is the log:
11/15/2017:11:13:27 AM – Granting ‘NT AUTHORITY\SYSTEM’ Full Control on: C:\Windows\WEB\Wallpaper\Windows\img0.jpg
11/15/2017:11:13:27 AM – Granting ‘DESKTOP-DB96555\Administrator’ Full Control on: C:\Windows\WEB\Wallpaper\Windows\img0.jpg
11/15/2017:11:13:27 AM – Replacing default wallpaper in: C:\Windows\WEB\Wallpaper\Windows\img0.jpg
11/15/2017:11:13:27 AM – Unable to replace default wallpaper: C:\Windows\WEB\Wallpaper\Windows\img0.jpg
Am i missing a step?
Jason
Hi Jason,
Sorry for the late reply. I’ve performed some tests in my lab environment and it’s not working for me neither. It only works when using it in MDT to create a reference image. I’ll have to put this on my todo list and see if I can come up with a solution that also works during deployment with ConfigMgr.
Regards,
Nickolaj
Hi Nickolaj. Thanks for this great work.
I’m applying it as a step in our OSD TS in SCCM 2012 1606. We’re deploying W10 1607.
The script seems to run successfully, as I see the appropriate img0.jpg placed and visible in the display settings, but it is not actually displaying the wallpaper on the desktop. We see the standard W10 ‘window’ image.
Here is the log:
10/4/2017:7:29:12 PM – Staging NTFSSecurity module in: C:\WINDOWS\System32\WindowsPowerShell\v1.0\Modules\NTFSSecurity
10/4/2017:7:29:12 PM – Setting new owner of ‘SIA-WKSTN-06\Administrator’ on: C:\WINDOWS\WEB\Wallpaper\Windows\img0.jpg
10/4/2017:7:29:12 PM – Granting ‘NT AUTHORITY\SYSTEM’ Full Control on: C:\WINDOWS\WEB\Wallpaper\Windows\img0.jpg
10/4/2017:7:29:12 PM – Granting ‘SIA-WKSTN-06\Administrator’ Full Control on: C:\WINDOWS\WEB\Wallpaper\Windows\img0.jpg
10/4/2017:7:29:12 PM – Replacing default wallpaper in: C:\WINDOWS\WEB\Wallpaper\Windows\img0.jpg
10/4/2017:7:29:12 PM – Unable to locate wallpapers in 4K root folder when processing, skipping the 4K wallpapers
Am I missing a step?
Thanks again for all the great work and sharing it with the community.
Colin
Hi Colin,
I’ve had some issues (not working at all since 1607/1703, not tested yet on 1709) where the script didn’t work at all. It seems that the process it uses it not valid anymore. I’ll circle back to this thread when I have more information on this issue.
Regards,
Nickolaj
I’m using MDT 8443 with Build 1703. Your script completes without error but does nothing. There is nothing in the log to indicate why. I even opened the folder “C:\Windows\Web\Wallpaper\Windows” and watched while it ran. The images did not change.
Hi Bill,
I’ve not used this in MDT, only when deploying machines with ConfigMgr. I’ll spin up a VM and see if there’s issues with this approach for Windows 10 version 1703.
Regards,
Nickolaj
unable to stage required Powershell Module Windowss 10 Ent v1607 can not seem to make it work Any idea ?
Nickolaj, an even easier way to do it is to overwrite the files after the WIM image has been applied but before the next reboot (so create the step between Apply Operating System and Apply Windows Settings). This way you don’t have change any permissions, and Windows will apply the proper permissions to your files on first boot!
Thanks for sharing! 🙂
Regards,
Nickolaj