Defining Window z-order “band”
For clarity, in this context the word “band” means group of z-orders
Until Windows 8, there was only one band, the ZBID_DESKTOP band, which is the default one when you write an application that creates a new window and when it gains focus it will go to the highest z-order meaning it will go on top of other windows. This is true unless there are topmost windows. As the name says, it will stay on top of other windows. What if there are 2 topmost windows? Well, in this case, the last window to gain focus will stay on top of other one. In the end there are 2 groups of z-order in the ZBID_DESKTOP band, normal and topmost. These will never be “touched” by each other, topmost window will always stay on top of other normal window, no matter what.
Starting from Windows 8, Microsoft “introduced” other bands, and all of them are higher z-orders than the desktop band. For example the start menu is on ZBID_IMMERSIVE_MOGO. Task manager “always on top” is on ZBID_SYSTEM_TOOLS. These are always on top of any window in the ZBID_DESKTOP band, that is, any normal or topmost window created by third parts developers will no longer cover start menu and stuff. These bands cannot mix each other, meaning, for example, that ZBID_DESKTOP will never be on top of ZBID_IMMERSIVE_MOGO, that is, bands have their z-orders too.
Like ZBID_DESKTOP, other bands also can be subdivided in 2 groups: normal and topmost window.
The order of bands are the following, from the lowest to the highest z-order (some ZBID orders are unknown for me ATM):
- ZBID_DESKTOP
- ZBID_IMMERSIVE_BACKGROUND
- ZBID_IMMERSIVE_APPCHROME
- ZBID_IMMERSIVE_MOGO
- ZBID_IMMERSIVE_INACTIVEMOBODY
- ZBID_IMMERSIVE_NOTIFICATION
- ZBID_IMMERSIVE_EDGY
- ZBID_SYSTEM_TOOLS
- ZBID_LOCK (Windows 10 only)
- ZBID_ABOVELOCK_UX (Windows 10 only)
- ZBID_IMMERSIVE_IHM
- ZBID_GENUINE_WINDOWS
- ZBID_UIACCESS
unordered band z-order
- ZBID_IMMERSIVE_INACTIVEDOCK
- ZBID_IMMERSIVE_ACTIVEMOBODY
- ZBID_IMMERSIVE_ACTIVEDOCK
- ZBID_IMMERSIVE_RESTRICTED (UNUSED)
Visualizing Window bands
ZBID_DESKTOP: this band is where all our window stays on. Let’s suppose you have 2 window here, Paint and Photos. Both are desktop windows, and they can overlap each other by just focusing a window or another. Now suppose that Paint window is topmost, now it will stay on top of Photos, regardless of its focus state.
ZBID_IMMERSIVE_MOGO: this band is used by the start menu AND the taskbar (only if the start menu is open).
ZBID_IMMERSIVE_NOTIFICATIONS: used by the Action Center, notifications and some system flyouts (e.g. Network, Volume).
ZBID_SYSTEM_TOOLS: used by Task Manager with “Always on Top” enabled, Alt-Tab view.
ZBID_ABOVELOCK_UX: used by “Playing Now”. This band and anything else higher will stay on top of the lock screen.
Since posting all ZBIDs would make this post XXL….
- ZBID_IMMERSIVE_SEARCH: Cortana/Windows Search
- ZBID_IMMERSIVE_INACTIVEMOBODY: Picture in picture (UWP CompactOverlay)
- ZBID_IMMERSIVE_APPCHROME: TaskView
- ZBID_GENUINE_WINDOWS: “Activate Windows”!
Technical Parts
ZBID enum
Windows 8.x / Windows 10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
enum ZBID { ZBID_DEFAULT = 0, ZBID_DESKTOP = 1, ZBID_UIACCESS = 2, ZBID_IMMERSIVE_IHM = 3, ZBID_IMMERSIVE_NOTIFICATION = 4, ZBID_IMMERSIVE_APPCHROME = 5, ZBID_IMMERSIVE_MOGO = 6, ZBID_IMMERSIVE_EDGY = 7, ZBID_IMMERSIVE_INACTIVEMOBODY = 8, ZBID_IMMERSIVE_INACTIVEDOCK = 9, ZBID_IMMERSIVE_ACTIVEMOBODY = 10, ZBID_IMMERSIVE_ACTIVEDOCK = 11, ZBID_IMMERSIVE_BACKGROUND = 12, ZBID_IMMERSIVE_SEARCH = 13, ZBID_GENUINE_WINDOWS = 14, ZBID_IMMERSIVE_RESTRICTED = 15, ZBID_SYSTEM_TOOLS = 16, //Windows 10+ ZBID_LOCK = 17, ZBID_ABOVELOCK_UX = 18 }; |
CreateWindowInBand
This is a private api function found in user32.dll. Since it’s not present in Windows SDK headers/libs, you have to use GetProcAddress to get access to that function.
CreateWindowInBand function is the same as CreateWindowEx except it has 1 more parameter, dwBand, that is where you specify the band on which the window should stay (ZBID).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
HWND WINAPI CreateWindowInBand( DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam, DWORD dwBand); |
CreateWindowInBandEx
Private api function found in user32.dll.
Same as CreateWindowInBand plus 1 parameter, dwTypeFlags
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
HWND WINAPI CreateWindowInBandEx( DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam, DWORD dwBand, DWORD dwTypeFlags); |
SetWindowBand
Private api function found in user32.dll.
SetWindowBand function has 3 parameters, hWnd, hWndInserAfter and dwBand. Same as 2 first parameters from SetWindowPos. The returned value indicates success or failure. In case of failure call GetLastError. I’m 99.9% sure you got a nice 0x5 (ACCESS DENIED)
1 2 3 4 |
BOOL WINAPI SetWindowBand( HWND hWnd, HWND hwndInsertAfter, DWORD dwBand); |
GetWindowBand
Private api function found in user32.dll.
GetWindowBand function has 2 parameters, hWnd and pdwBand. pdwBand is a pointer that receives the band (ZBID) of the HWND. The returned value indicates success or failure. In case of failure call GetLastError.
1 2 3 |
BOOL WINAPI GetWindowBand( HWND hWnd, PDWORD pdwBand); |
Can I call these APIs?
Short reply: Some yes (under certain conditions), some no.
- GetWindowBand works in every case, you can use it without any problem.
- CreateWindowInBand/Ex works ONLY if you pass ZBID_DEFAULT or ZBID_DESKTOP as dwBand argument. Also ZBID_UIACCESS is permitted only if the process has UIAccess token (obtainable, for example, by setting uiAccess=true in app.manifest, more info here). Any other ZBID will fail with 0x5 (ACCESS DENIED).
- SetWindowBand will never work, it will always fail with 0x5 (ACCESS DENIED).
Why I can’t call using other ZBIDs?
Because Microsoft added extra checks for these bands.
For CreateWindowInBand/Ex, to be able to use more ZBIDs, the program must have a special PE header, named “.imrsiv” ( bss_seg), flagged with IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY and be signed with a Microsoft certificate “Microsoft Windows”.
For SetWindowBand, well… currently I don’t know how this works internally, but it has extra checks. Explorer calls it via twinui.pcshell.dll
This function lets change the band of a window and, for Windows Explorer’s case, bring the taskbar from ZBID_DESKTOP to ZBID_IMMERSIVE_MOGO (and vice versa).
Is there a way to bypass it? You did it, so I guess yes?
Yes…ish. You will not like the way I did it but that’s (currently) the only way.
- To use CreateWindowInBand with any ZBID, you need to inject a dll into an immersive process. In my case I used RuntimeBroker (explorer.exe would crash easily). An code example can be found here: launcher (to inject a dll) and the dll
- To use SetWindowBand, you need to inject a dll into explorer.exe and hook SetWindowBand (yep, a trampoline). As soon as you press the Start button the hooked function will be called and you can call the original function with a different hwnd target. This sucks but oh, I haven’t found yet a better way.
Conclusion
Starting from Windows 8 Microsoft “introduced” the concept of band, a group of z-order that they will never “touch” each other. There are many bands but for developers, only ZBID_DESKTOP (default band for CreateWindow/Ex) is accessible.
So useful info! Thank you. I found this function like 3 years ago but I couldn’t make it work, tried calling but it always failed then after some research I thought only signed apps can do that and gave up 😀 I should have tried injection nice work.
I found a better way to call SetWindowBand that doesn’t require hooking. You need to call NtUserEnableIAMAccess before calling setwindowband, then it works.
Are you sure? Because I did try that without success. NtUserEnableIAMAccess requires a uint64 “key” as param, and that key is tied to an unique process that acquired it. Maybe you found a way to make it work (?)
Can You please post example how did You call NtUserEnableIAMAccess?
I just noticed that inspect.exe uses setwindowband when we check/uncheck “options->always on top”. I cannot reverse engineer programs, but may be You can do it.
https://github.com/guolaok/Python-UIAutomation-for-Windows/tree/master/inspect
Looks like the IAM access key is generated in NtUserSetShellWindowEx, and can be obtained only once using NtUserAcquireIAMKey.
When explorer start, it sets itself as shell window, and then twinui.pcshell.dll got that key. Then nobody can get that key again.
I don’t know what will happen if another program call SetShellWindowEx and get the new key. I guess the old one will become invalid.
After a test I found that if explorer is running, SetShellWindowEx will fail.
The best solution I think is kill current explorer, start a new one and inject a DLL, hook NtUserAcquireIAMKey and get that key.
@Cloudy. Can you share how you’re calling NtUserEnableIAMAccess?
Very interesting.
But how does work on-screen keyboard osk.exe?
It has ZBID_UIACCESS but is in front of any window.
Even FullScreen DirectX programs do not run when osk running.
ZBID_UIACCESS is the highest band you can get, which is why it is in front of any window.
Thank You!
Dont You know, what dwTypeFlags mean and what is its enumeration in CreateWindowInBandEx?
For me it works only dwTypeFlags=0 or dwTypeFlags=4.
If dwTypeFlags>=8, I get ERROR_INVALID_PARAMETER.
I never dug on that, so I don’t know what it does… yet.
On-screen keyboard use ZBID_UIACCESS too. I checked it.
Thank you very much. It helps me a lot.
Recently, I have been working on a desktop watermarking function, which needs to be displayed on top of other Windows. However, I have a problem: I cannot cover the full screen player window. Do you know why?
If player use directx then You need to inject and hook it.
Do you know how CSRSS message boxes (the one that appears when you send message to some user with Process Explorer etc.), Alt-Tab, and TabTip.exe shows on top of other topmost windows in Win7?
In Win8+ CSRSS windows still shows on top of other topmost windows but not of any other bands.
To CreateWindowInBand We can steal a token from system process.
https://github.com/killtimer0/uiaccess