MSEndpointMgr

Set custom wallpaper in Windows 10 during OSD with ConfigMgr

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
  • 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.

Nickolaj Andersen

Chief Technical Architect and Enterprise Mobility MVP since 2016. Nickolaj has been in the IT industry for the past 10 years specializing in Enterprise Mobility and Security, Windows devices and deployments including automation. Awarded as PowerShell Hero in 2015 by the community for his script and tools contributions. Creator of ConfigMgr Prerequisites Tool, ConfigMgr OSD FrontEnd, ConfigMgr WebService to name a few. Frequent speaker at conferences such as Microsoft Ignite, NIC Conference and IT/Dev Connections including nordic user groups.

13 comments

  • 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.

  • 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!

Sponsors