Rotate Intune Wallpaper and Lock Screen Images with the Seasons using Azure Automation

G’day! Hope you’re all well. Bit of a fun one today. Ever fancied your users logging in to a frosty wallpaper in December, something sunny come June and something festive over Christmas, all without you having to touch a thing? In this post, I’ll be rotating an Intune wallpaper and lock screen image with the seasons using an Azure Storage account, Azure Automation and a bit of Graph API, set it up once and it just ticks over in the background year after year.

I’m using the seasons of the year for the sake of this demo, but you’re not stuck with that. You could use the exact same setup to rotate images for a corporate promotion, or swap them out monthly based on company news, it’s just a case of changing the image names and schedules to suit your needs.

The problem with a static Intune wallpaper

Setting a corporate desktop wallpaper and lock screen through the Intune Settings Catalog is easy enough, the catch is that the image you set is the image you’re stuck with until somebody remembers to change it. Nobody ever does. Which is how you end up with snow on the lock screen in July.

There’s also a technical gotcha that trips a lot of people up, the Personalization CSP only downloads the image again when the URL changes. So if you were thinking of overwriting a single current.png in blob storage every few months, I’m afraid Windows will happily carry on showing the cached copy. The URL in the profile has to change, which means the profile itself has to change, and that’s where Azure Automation and Graph come in.

How it all fits together

There are four moving parts:

  1. An Azure Storage account holding the seasonal images in a blob container.
  2. A Settings Catalog profile in Intune that sets the Desktop Image URL and Lock Screen Image URL.
  3. An Azure Automation runbook, kicked off by a schedule for each season (plus Christmas if you fancy it) that fires once a year, which uses Graph to swap the URL’s in the profile.
  4. Devices picking up the new URL’s the next time they check in with Intune and downloading the new images.

The Automation account authenticates with a system assigned managed identity, so there’s no client secret or certificate to keep renewing.

Prerequisites

  • An Azure subscription where you can create a Storage account and an Automation account
  • Intune Administrator access
  • Windows 11 (or 10) Enterprise or Education on your devices
  • Your wallpaper images in JPG or PNG, I would recommend 3840×2160 (more on that in the gotchas)
  • Privileged Role Administrator or Global Administrator for the one off Graph permission grant

Hosting the images in Azure Storage

First up, the devices need somewhere to download the images from. Create a Storage account (Standard LRS is plenty, this is hardly mission critical data), add a container called wallpapers and upload your images: spring.png, summer.png, autumn.png and winter.png, plus christmas.png if you’re joining in with the festivities. Keep the names all lowercase, more on why in the gotchas.

I’m using PNG throughout but JPG works just as well, just keep every image in the same format, as the runbook builds the URL’s from a single file extension that you’ll set in one variable later on. Once uploaded, your container should look something like this:

Azure Storage wallpapers container showing spring, summer, autumn, winter and christmas PNG images uploaded for the Intune wallpaper
All five wallpapers uploaded and ready to go.

If you just want to follow along and haven’t got any images to hand yet, I’ve put together a set of 4K wallpapers with the Modern Workspace Hub logo on, already named to match the runbook. Feel free to grab them here:

You’ve then got two options for making the images reachable. I’ve gone with Option B for this demo so that’s what the screenshots show, Option A is only a couple of settings anyway.

Option A: public container

The simple route, anyone with the URL can view the images, which for wallpaper is usually fine:

  1. On the Storage account, go to Settings, then Configuration and set Allow Blob anonymous access to Enabled.
  2. On the wallpapers container, set the anonymous access level to Blob (anonymous read access for blobs only).

Your image URL’s will look like so:

https://yourstorageaccount.blob.core.windows.net/wallpapers/winter.png

Option B: private container with a SAS token

If your security team (or your own conscience) won’t allow anonymous access, keep the container private and use a read only SAS instead.

First, on the Storage account go to Settings, then Configuration and check Allow storage account key access is set to Enabled, you’ll need it for the SAS in a minute. Leave Allow Blob anonymous access on Disabled. If you went with Option A, this is the same page you’d enable anonymous access on:

Azure Storage account configuration with Allow storage account key access enabled and Allow Blob anonymous access disabled for Intune wallpaper images
Key access on, anonymous access off.

Next, open the wallpapers container and select Shared access tokens. The Signing method drop-down gives you two options, and the one you pick matters here:

Signing MethodSigned withLongest Expiry
Account keyThe Storage account’s access keyNo limit, years if you want
User delegation keyA temporary key from your Entra ID sign-in7 days

Set Permissions to Read only, pick a long expiry (a few years out, and stick a reminder in your calendar), leave Allowed protocols on HTTPS only and select Generate SAS token and URL, here’s mine:

Azure Storage shared access token settings using Account key signing, Read permission and a long expiry for the Intune wallpaper container
A read only SAS signed with the account key, good for a few years.

Copy the Blob SAS token somewhere safe, it goes into the Automation account later on. Your URL’s then become:

https://yourstorageaccount.blob.core.windows.net/wallpapers/winter.png?<sas-token>

Keep your storage account name (and SAS token if you went with Option B) to hand, you’ll need them in the next bit.

Creating the Intune wallpaper profile

Over in the Intune admin centre, go to Devices, then Configuration, select Create, then New policy, choose Windows 10 and later for the platform and Settings catalog for the profile type.

Give it a name, I’ve gone with PROD – Seasonal Wallpaper and Lock Screen, note it down exactly as the runbook finds the profile by name. Select Add settings, search for Personalization and tick both Desktop Image URL and Lock Screen Image URL, then enter in the URL of your current season’s image for both (or different images for each if you’re feeling fancy).

If you went with Option A, that’s just the plain blob URL:

https://yourstorageaccount.blob.core.windows.net/wallpapers/autumn.png

For Option B, you don’t need to build any of the SAS part yourself, it’s the Blob SAS token you copied earlier, all of it, from sp= right through to the end of the signature. Stick a ? on the end of the image URL and paste the token straight after it (if your token already starts with a ?, don’t add a second one). It should end up looking like this:

https://yourstorageaccount.blob.core.windows.net/wallpapers/autumn.png?sp=r&st=2026-10-30T11:39:50Z&se=2030-10-30T19:54:50Z&spr=https&sv=2026-02-06&sr=c&sig=<signature>

Don’t paste in the Blob SAS URL from the portal as is, as you generated the SAS on the container it points at the container rather than an image. Add the image name in before the ? yourself.

Assign it to a test device group and create the profile, it should look something like this:

Intune wallpaper profile in the Settings Catalog showing Desktop Image URL and Lock Screen Image URL under Personalization
The Settings Catalog profile with both Personalization URLs pointing at the storage account.

Test it on a device before going any further and ensure all is well. In my lab it took roughly 10 minutes to show up after forcing a sync. The new wallpaper doesn’t always kick in mid-session, so if the profile shows as succeeded but the desktop hasn’t changed, sign out and back in again (lock the device to check the lock screen). If it still doesn’t show up, check the Windows edition before blaming anything else.

The Automation account and Graph permission

Create an Automation account, then under Account Settings, Identity, make sure the system assigned managed identity is set to On and copy the Object ID, like so:

Azure Automation account Identity blade with the system assigned managed identity turned on and the Object ID shown for the Intune wallpaper runbook
Managed identity switched on, Object ID copied.

The managed identity needs the DeviceManagementConfiguration.ReadWrite.All application permission on Microsoft Graph. You can’t grant Graph app roles to a managed identity through the portal, so it’s a quick bit of PowerShell from your admin workstation, swap in the Object ID you copied:

<#
.SYNOPSIS
    Grants DeviceManagementConfiguration.ReadWrite.All to an Automation account managed identity.
.DESCRIPTION
    Assigns the Microsoft Graph application permission needed by the seasonal
    wallpaper runbook to the Automation account's system assigned managed identity.
.NOTES
    Author: Alex Durrant
    Version: 1.0
#>

Connect-MgGraph -Scopes "Application.Read.All","AppRoleAssignment.ReadWrite.All"

$miObjectId = "<your managed identity Object ID>"
$graphSp = Get-MgServicePrincipal -Filter "appId eq '00000003-0000-0000-c000-000000000000'"
$appRole = $graphSp.AppRoles | Where-Object { $_.Value -eq "DeviceManagementConfiguration.ReadWrite.All" }

New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $miObjectId `
    -PrincipalId $miObjectId `
    -ResourceId $graphSp.Id `
    -AppRoleId $appRole.Id
PowerShell

Once that’s run, you can check it in Entra ID under Enterprise applications, change the application type filter to Managed Identities, open the Automation account and select Permissions:

Entra ID permissions blade showing DeviceManagementConfiguration.ReadWrite.All granted to the Automation account managed identity
The managed identity with the Graph permission granted, no secrets or certificates in sight.

One permission and nothing to expire, this is why I love managed identities.

If you went with Option B, now’s the time to store that SAS token. In the Automation account go to Shared Resources, then Variables, select Add a variable, call it WallpaperSasToken, set the type to String, paste in the token and set Encrypted to Yes:

Azure Automation encrypted variable named WallpaperSasToken storing the SAS token for the Intune wallpaper runbook
The SAS token tucked away in an encrypted variable.

The runbook

Create a new runbook in the Automation Account, I’ve called mine Update-SeasonalWallpaper. Set the Runbook type to PowerShell and pick the newest PowerShell 7 Runtime version in the list, which was 7.2 in my case. If your account has been switched over to the newer Runtime environments experience you’ll get PowerShell 7.4 instead, the script works on either. Az.Accounts comes with the PowerShell 7 runtimes so there shouldn’t be anything to import, but check under Modules if the script complains.

The script takes one parameter, the season (or theme) to apply, which lines up with an image name in your container. It finds the profile through Graph, swaps in the new URL’s and writes the profile back. If the URL’s are already correct, it exits without touching anything.

Graph hands results back in pages, so the script follows the @odata.nextLink trail until it has every policy, then picks yours out by name. I’d steer clear of using $filter on this endpoint, it can return empty pages with a nextLink before it gets to your policy, and if you only read the first page you’ll get a false “not found”.

Copy and paste the below into the runbook and change the variables at the top to suit your environment:

<#
.SYNOPSIS
    Updates the Intune wallpaper profile with the image for the given season.
.DESCRIPTION
    Triggered by Azure Automation schedules (one per season), each passing in the season
    (or theme) to apply. Updates the Desktop Image URL and Lock Screen Image URL
    values in the target Settings Catalog profile via Microsoft Graph.
.PARAMETER Season
    The season or theme to apply. Must match an image name in the container, e.g. winter.
.NOTES
    Author: Alex Durrant
    Version: 1.0
#>

param(
    [Parameter(Mandatory = $true)]
    [string]$Season
)

# Variables, change these to suit your environment
$policyName = "PROD - Seasonal Wallpaper and Lock Screen"
$baseUrl    = "https://yourstorageaccount.blob.core.windows.net/wallpapers"
$fileType   = "png"   # change to jpg if that's what you've uploaded

# Option B only, uncomment if using a private container with a SAS token
# stored as an encrypted Automation variable
# $sasToken = Get-AutomationVariable -Name "WallpaperSasToken"

$imageUrl = "$baseUrl/$($Season.ToLower()).$fileType"
if ($sasToken) { $imageUrl = "${imageUrl}?$($sasToken.TrimStart('?'))" }
Write-Output "Applying the $Season image to the policy"

# Authenticate with the managed identity and grab a Graph token
Connect-AzAccount -Identity | Out-Null
$token = (Get-AzAccessToken -ResourceUrl "https://graph.microsoft.com").Token
# Newer versions of Az.Accounts return the token as a SecureString
if ($token -is [securestring]) { $token = $token | ConvertFrom-SecureString -AsPlainText }
$headers = @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" }
$graph = "https://graph.microsoft.com/beta"

# Graph returns results in pages, keep following @odata.nextLink until there are none left
function Get-GraphPagedResult {
    param([string]$Uri)
    $results = @()
    while ($Uri) {
        $page = Invoke-RestMethod -Uri $Uri -Headers $headers
        $results += $page.value
        $Uri = $page.'@odata.nextLink'
    }
    return $results
}

# Find the Settings Catalog policy by name (matched here rather than with $filter,
# which can return empty pages on this endpoint)
$policy = Get-GraphPagedResult -Uri "$graph/deviceManagement/configurationPolicies" |
    Where-Object { $_.name -eq $policyName } | Select-Object -First 1
if (-not $policy) { throw "Policy '$policyName' not found." }
$policyId = $policy.id

# Retrieve the current settings (paged for the same reason)
$settings = Get-GraphPagedResult -Uri "$graph/deviceManagement/configurationPolicies('$policyId')/settings"

# Update both Personalization URL values
$changed = $false
foreach ($setting in $settings) {
    $defId = $setting.settingInstance.settingDefinitionId
    if ($defId -like "*personalization_desktopimageurl" -or $defId -like "*personalization_lockscreenimageurl") {
        if ($setting.settingInstance.simpleSettingValue.value -ne $imageUrl) {
            $setting.settingInstance.simpleSettingValue.value = $imageUrl
            $changed = $true
        }
    }
}

if (-not $changed) {
    Write-Output "URLs already set for $Season, nothing to do."
    return
}

# Write the policy back with the updated settings
$body = @{
    name         = $policy.name
    description  = $policy.description
    platforms    = $policy.platforms
    technologies    = $policy.technologies
    roleScopeTagIds = $policy.roleScopeTagIds   # keeps your scope tags, a PUT resets them otherwise
    settings        = $settings
} | ConvertTo-Json -Depth 50

Invoke-RestMethod -Method PUT -Uri "$graph/deviceManagement/configurationPolicies('$policyId')" -Headers $headers -Body $body
Write-Output "Policy updated successfully to $Season."
PowerShell

Before publishing the runbook, open the Test pane, enter in a season for the Season parameter and select Start. Use a different season to the one already in your profile, otherwise the runbook will just tell you there’s nothing to do, for me it was summer:

Azure Automation runbook test pane output confirming the Intune wallpaper policy was updated to the summer image
 Test run complete, summer’s come early.

Then pop back over to the profile in Intune and give it a refresh, both URL’s should now point at the new image:

Intune Settings Catalog wallpaper profile showing Desktop Image URL and Lock Screen Image URL updated to the summer image by the Azure Automation runbook
Same profile, new URL’s, no clicking required.

If all is well, publish it. Run the test once more with your actual current season if you don’t want your test devices sat on a summer wallpaper in the middle of autumn.

Scheduling it

Last bit. Automation schedules don’t have a yearly option, so the trick is a schedule per image that recurs every 12 Months, all linked to the same runbook.

Once the runbook’s published, open it and select Link to schedule, then Link a schedule to your runbook, then Add a schedule. Fill in the New Schedule pane like so, I’ve used spring as the example:

SettingValue
NameSeasonal Wallpaper – Spring
Starts1 March, 06:00
Time zone<<Your time zone>>
RecurrenceRecurring
Recur every12 Months
Set expirationNo

Leave Monthly occurrences alone. I’ve gone with 06:00 so the change is done and dusted before anyone logs on. Double check the time zone too, it defaults to whatever your browser reports, which isn’t always what you’d expect. The start date has to be in the future, so for any season that’s already started this year, put next year’s date in.

Azure Automation new schedule pane for the Intune wallpaper runbook set to recur every 12 months starting 1 March
Every 12 months is as close to yearly as Automation gets.

Select Create, then back on the Schedule pane select Parameters and run settings, enter in spring for Season and select OK, then OK again to link it:

Azure Automation link a schedule pane with the Season parameter set to spring for the Intune wallpaper runbook
The Season parameter is what tells the runbook which image to use.

Rinse and repeat for the rest. Here’s mine, UK meteorological seasons with a festive bonus:

Starts onRecurs everySeason
1 March12 monthsspring
1 June12 monthssummer
1 September12 monthsautumn
1 December12 monthschristmas
2 January12 monthswinter

The dates only live in the schedules, so it’s entirely up to you. In the Southern Hemisphere? Flip them around. Fancy a Halloween wallpaper for October, or something for a company milestone? Upload the image, add a schedule with the matching name and you’re done, no code changes needed.

Azure Automation runbook schedules blade showing seasonal wallpaper schedules recurring every 12 months including Christmas
Five schedules, one runbook. The Season parameter does the rest.

One trade off with this approach, if a scheduled run is ever missed, the wallpaper stays on the previous image until the next schedule fires, nothing corrects itself in between. In practice Automation schedules are very reliable and you can always start the runbook manually, which is worth doing now with your current season to get everything in line.

Devices pick up the new URL’s the next time they check in with Intune, usually within 8 hours, and because the URL has changed, the CSP downloads the new image instead of serving up the cached copy.

Gotchas

  • Users can’t change their wallpaper. Setting these CSP’s locks personalisation down, so make sure you’ve had that conversation with the business first.
  • Graph Explorer and paging. If you’re testing filters in Graph Explorer, an empty value with an @odata.nextLink just means there are more pages to go. Either page through it, or grab the policy GUID from the address bar in the Intune portal and skip the search entirely.
  • Lowercase file names. Blob names are case-sensitive (unlike Windows), and the runbook lowercases the season it’s given. Upload Autumn.png and the profile ends up pointing at autumn.png, which doesn’t exist, so the wallpaper simply doesn’t change and nothing tells you why. I hit this problem in my lab and wondered what was going on!
  • Image size. One image gets stretched \ cropped to fit every screen, so go with 3840×2160 (4K, 16:9), it scales down cleanly to 1080p and 1440p. Keep the main subject in the middle of the image so it survives cropping on 3:2 screens like the Surface range. Scaling down always looks fine, scaling up never does.
  • SAS expiry and key rotation (Option B). When the token expires, or the storage account keys are rotated (the SAS is signed by an account key behind the scenes), wallpapers quietly stop downloading and nothing will tell you. Set a long expiry and put a reminder in your calendar to renew it and update the Automation variable.

Frequently asked questions

Why is my Intune desktop wallpaper not applying? Nine times out of ten it’s the Windows edition. The Personalization CSP does nothing on Windows Pro, it needs Enterprise or Education. If the edition’s right, check the device can actually reach the image URL, and if you’re using a SAS token, that it hasn’t expired.

Can I set the Intune lock screen image as well as the desktop wallpaper? Yes, both live in the same Personalization category in the Settings Catalog, Desktop Image URL and Lock Screen Image URL, and the runbook in this post updates both in one go.

Why doesn’t overwriting the image file update my devices? The Personalization CSP only downloads the image again when the URL changes. Overwrite winter.png behind the same URL and devices will keep showing the cached copy, which is why this rotates the URL in the profile instead of the file in storage.

How quickly do devices pick up the new wallpaper? The next time they check in with Intune, usually within 8 hours. For testing, you can kick off a sync from the Company Portal or the Intune admin centre to hurry one along.

Wrapping up

And that’s it, an Intune wallpaper that looks after itself. It’s only a small thing, but it’s the sort of thing end users actually notice.

While you’re tidying up Intune, I’ve also written about bulk renaming Intune policies, handy if your profile names are a bit all over the place.

As always, test on a pilot group before letting it loose on production. If you’ve got any questions or run into any issues, drop a comment below.

Happy decorating! 👌

Leave a Reply...

Scroll to Top