Changing the image used in the "Force a specific default lock screen and logon image" policy
Customer called that was using the "Force a specific default lock screen and logon image" policy on Windows 8.1 to do exactly what the policy says. After setting this up, they wanted thought it would be cool to periodically change the image. They did not want to have to go through all the steps to change the policy, so they just wanted to overwrite the file that was being used in the policy with a different image. They tested doing this and found that the replacing the image did update the lock screen, so... they called support. The support engineer they were working with tested this and found that it did not work for them either.
This is the point I was engaged with "Hey, there is a bug in Windows, this policy does not work". Let me start by saying I have never used this policy or troubleshot this policy, so I did not have any background of how it worked, so I took the engineers comments at face value. Here is my conversation with the support engineer:
- Me: Does this work on Windows 10?
- Other support guy: They are not using Windows 10
- Me: Fair enough, can you test to see if works on Windows 10?
- Other support guy: Why? They are not running Windows 10.
- Me <slightly frustrated>: Well.. if it works on Windows 10, we will know this issue was something we found and fixed. If it does not work on Windows 10, we will know this has always been this way, and we will make a decision on what to do next from there.
- Other support guy: OK...
- Other support guy: OK.. I tested it and it works on some version of Windows 10, and does not work at all on others.
- Me: What do you mean?
- Other support guy: I tested it on 1703 and it worked, but it does not work at all on 1709 or 1803 (see here for a quick decoder ring of these build numbers)
- Me: What do you mean when you say it "does not work at all"?
- Other support guy: The background image I am trying to use never gets applied to the lock screen.
My internal monologue: Why would this policy work on some version of Windows 10, and not all of them? Why would it work on an earlier version and be broken in later versions. I go back and read the policy help from top to bottom.
- Me: Can you send me a screen shot of winver.exe from your 1709 and 1803 machines
- Other support guy: Sure
I get email with screenshots...
- Me: Your 1709 and 1803 machines are Windows 10 Professional.
- Other support guy: So.
- Me: Read the last line in the group policy description (for reference it reads: "Note: This setting only applies to Enterprise, Education, and Server SKUs")
- Other support guy: Oh.
- Other support guy: OK. It seems to work on all versions of Windows 10.
- Me: OK, good to know.
Here is where we stand based of what we learned so far:
- The policy does exactly what it says it will do in all OS that support the policy. The policy does not state that you should be able to change the image using the exact same name in the location where the file is stored and the OS will know it is different and use it. The policy says "If you want to pick A particular image for a lock screen, we can do that". From a technical perspective, my inner-nerd thinks what we are doing here should work, but the policy does not infer or imply that the image change part of this is going to work.
- The act of replacing of the image with an image of the same name and having the OS automatically pickup the new image does not work on Windows 8.1, but does work on Windows 10.
Quick call with the customer and talked through our findings. Customer said they would do some testing on Windows 10 and report back. Customer follows up and says they cannot get this to work on any version of Windows 10.
My internal monologue: Seriously... I did not do the testing, the other support guy did. Why do I trust anyone? :)
Now I crank up my VM of Windows 10 and run through the test. Works like a champ. Email the customer and confirm it is working and explain the steps I am using. Customer comes back and states that these steps are not working for him. Hmm... back to my VM. I test it again, and this time mine does not work. What is going on here? I had created by backgrounds just by opening up paint and making an image, saving it to folder 1, then modifying it and saving a second image to folder 2, using the same name as the image in folder 1. Now that I am questioning everything, I deleted my previously created images and made new ones. I now have lockScreen.jpg images stored in two different folders, one is just a different color background. I run through the test taking the first image and putting it on the desktop then modifying the policy to use the image on the desktop and running gpupdate /force. I lock the desktop, and my image is on the lock screen. I then take the image out of folder 2 and replace the image on the desktop and then lock the desktop. The image on the lock screen changes to my second image. So far, so good. Just to be sure, let's do this again. I take the image out of folder 1 and replace the image on the desktop. I lock the desktop... and... still has the image from folder 2.
Why did it work the first time, but not the second time? On a hunch, I right clicked on the image on the desktop, went to properties and over to the details tab and edited the "Comments" meta-data field. Just typed in "foo". Clicked OK and locked my desktop. Boom! The image switched to the updated lockScreen.jpg. This test makes me think that if the image is older (last modified date) than the one we currently have, the OS ignores it.
In order to be sure I understand how this works, I wanted to find the code that updates the images. In order to do this I setup Sysinternal's Process Monitor (Procmon) to monitor for my image name. I kicked off a trace and locked my machine and unlocked it. I then looked at the trace for what process was interacting with my image. It was LogonUI.exe and from this I was able to get the callstack for the interaction with my image. Here is what I found:
0 FLTMGR.SYS FltpPerformPreCallbacks+0x2dc
1 FLTMGR.SYS FltpPassThroughInternal+0x8c
2 FLTMGR.SYS FltpCreate+0x2c9
3 ntoskrnl.exe IofCallDriver+0x59
4 ntoskrnl.exe IopParseDevice+0x773
5 ntoskrnl.exe ObpLookupObjectName+0x73b
6 ntoskrnl.exe ObOpenObjectByNameEx+0x1df
7 ntoskrnl.exe NtQueryFullAttributesFile+0x19e
8 ntoskrnl.exe KiSystemServiceCopyEnd+0x13
9 ntdll.dll ZwQueryFullAttributesFile+0x14
10 KernelBase.dll GetFileAttributesExW+0x95
11 Windows.UI.Immersive.dll CLockScreenHistory::_GPCacheStateFromEntry+0x49
12 Windows.UI.Immersive.dll CLockScreenHistory::_InitOrUpdateGPImages+0x80
13 Windows.UI.Immersive.dll CLockScreen::_GetImageForUser+0x112a
14 Windows.UI.Immersive.dll CLockScreen::GetImageRandomAccessStreamForUser+0x70
15 Windows.UI.Immersive.dll CLogonUILockScreenStore::GetImageRandomAccessStreamForUser+0x82
16 Windows.UI.Logon.dll <lambda_80e166d0c492f39ef05dd1f43223b1ab>::operator()+0x13a
17 Windows.UI.Logon.dll std::_Func_impl<std::_Callable_obj<<lambda_d638150044d2603ed2163f9c3c9c7178>
18 Windows.UI.Logon.dll std::_Func_class<Windows::Storage::Streams::IRandomAccessStream ^ __ptr64
19 Windows.UI.Logon.dll Concurrency::task<Windows::Storage::Streams::IRandomAccessStream ^ __ptr64>::_InitialTaskHandle<Windows::Storage::Streams::IRandomAccessStream ^ __ptr64
20 Windows.UI.Logon.dll Concurrency::details::_PPLTaskHandle<Windows::Storage::Streams::IRandomAccessStream ^ __ptr64
21 Windows.UI.Logon.dll Custom::_RunChoreBridge+0x21
22 Windows.UI.Logon.dll Windows::System::Threading::WorkItemHandler::[Windows::System::Threading::WorkItemHandler::__abi_IDelegate]::__abi_Windows_System_Threading_WorkItemHandler___abi_IDelegate____abi_Invoke+0x31
23 threadpoolwinrt.dll Windows::System::Threading::CThreadPoolWorkItem::CommonWorkCallback+0xaa
24 threadpoolwinrt.dll Windows::System::Threading::CThreadPoolWorkItem::BatchedCallback+0x88
25 ntdll.dll TppWorkpExecuteCallback+0x13c
26 ntdll.dll TppWorkerThread+0x6f9
27 kernel32.dll BaseThreadInitThunk+0x14
28 ntdll.dll RtlUserThreadStart+0x21
We can see that we have a lock screen history class that we are in. Off to the source. The source shows that LogonUI keeps a cache of the specified image. Each time we load the lock screen, we first make sure we can get to the image that is specified in the policy. Getting to the file might not be possible if it is hosted on a corporate network share and the user is at Starbucks. If we can get to the file, we then see if we already have a cached copy of the image. If we were able to both access the file AND we find that we already have the image in the cache, we use the CompareFileTime API to see if the modified date is earlier, same, or later than the one in the cache. If the image is newer (later in time) than the one in the cache, we update the file in the cache.
This explains why my testing would work then fail. When I created my images I created the first one, then created the second one. When I tried to use them in that order, it worked. But when I tried to toggle back from the second image to the first image, the date modified for the first file is before then one in the cache, and the OS does not update the cache, leaving us with a lock screen that does not change. So... why didn't this work on Windows 8.1? Back to the source for Windows 8.1 and we find the source is the same. Boot a Windows 8.1 machine, run through a test with what we know now, and sure enough, it works there to.
There are a lot of lessons to learn from this one:
- Don't assume the problem presented in accurate (Trust, but verify).
- Just because something does not work, does not mean it is a bug.
- Just because it worked once, does not mean it will always work.
- When you are not sure what data to collect, collect a Procmon. This is one of the best tools for reversing what is responsible for what and how these components work.
- Jason E