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:
191_1

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:
191_2

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:
191_3

Once you deploy Windows 10 from now on, you’ll get your custom wallpaper instead of the default one.

Nickolaj Andersen
Principal Consultant 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. Currently working for TrueSec as a Principal Consultant. 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 and user groups.

(3109)

comments
  • Nathan McNulty
    Posted at 23:38 March 30, 2016
    Nathan McNulty
    Reply
    Author

    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!

    • Nickolaj
      Posted at 21:50 April 7, 2016
      Nickolaj
      Reply
      Author

      Thanks for sharing! 🙂

      Regards,
      Nickolaj

  • Carl
    Posted at 15:11 September 1, 2016
    Carl
    Reply
    Author

    unable to stage required Powershell Module Windowss 10 Ent v1607 can not seem to make it work Any idea ?

  • Bill Westrup
    Posted at 18:01 September 8, 2017
    Bill Westrup
    Reply
    Author

    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.

    • Nickolaj Andersen
      Posted at 10:44 September 13, 2017
      Nickolaj Andersen
      Reply
      Author

      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

  • Leave a Reply