How Windows App Compatibility Works — Keeping Old Apps Alive with Compatibility Mode, Shims, and Compatibility Administrator
· Go Komura · Windows, Compatibility Mode, Shims, Application Compatibility, Compatibility Administrator, Legacy Asset Reuse, Windows Development, Existing Systems
“A ten-year-old business app whose source code is gone will not start on a new Windows 11 PC. I checked ‘Windows XP’ on the Compatibility tab of the properties dialog and it just worked. — What is that actually doing? Is it all right to keep relying on this?” That is a consultation we hear often.
When a single checkbox makes something work, it is natural to feel uneasy. The real identity of Compatibility mode, which looks like magic, is a collection of small pieces of code called shims that sit between the app and the Windows API and return a “lie”. Windows itself uses this stopgap at scale to keep apps from many generations ago running, and it exposes part of the mechanism to users and administrators.
Used without understanding the mechanism, life-extension becomes an unstable “we must not touch it because we do not know why it works”. Understand the mechanism and you can decide, with reasons, how far you can safely rely on it, what will break it, and when you should rewrite.
flowchart TB
accTitle: Understanding the mechanism changes the quality of life-extension
accDescr: Using Compatibility mode without understanding the mechanism leads to unstable life-extension you dare not touch; understanding the mechanism lets you decide with reasons how far you can rely on it, what will break it, and when you should rewrite
unknown["Use without understanding the mechanism"] --> fear["Unstable life-extension you dare not touch"]
known["Use after understanding the mechanism"] --> judge["Decisions with reasons"]
judge -.-> j1["How far you can rely on it"]
judge -.-> j2["What will break it"]
judge -.-> j3["When you should rewrite"]
Figure 1: Even for the same life-extension, the quality differs between anxiety from not knowing the mechanism and a decision based on understanding it.
Aimed at IT staff in small and medium businesses and at Windows app developers who look after old business apps, this article organizes, from Microsoft Learn primary sources, the shim mechanism that is the real identity of Compatibility mode, what the representative shims can do, how to apply them organizationally with Compatibility Administrator, the limits shims cannot save, and how to decide between life-extension and migration.
1. The Bottom Line First
- The real identity of Compatibility mode is shims (a compatibility layer). Settings from the Compatibility tab are written to
HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers, and a bundle of shims is applied to that process at startup.12 - A shim is a user-mode API hook that rewrites the import address table (IAT). It intercepts the path by which the app calls Windows APIs and returns the same answers old Windows would have. It does not change the OS itself.3
- What a shim can do is the same range as what a code fix in the app can do. It cannot bypass security mechanisms, and it cannot fix kernel-mode (device-driver) problems.3
- Microsoft ships a large number of ready-made shims — version lies, file-path remapping, registry spoofing, spoofing of administrator checks, and more. You can apply them to individual EXEs from Compatibility Administrator.4
- Windows itself uses shims by default. The OS-standard compatibility database (.sdb) is matched on every launch, and PCA (Program Compatibility Assistant) can also detect a problem and apply a compatibility setting automatically.15
- “Answering as if it were an older Windows” is now the default. From Windows 8.1 onward,
GetVersionExdoes not return an OS version the app has not declared in its manifest. Compatibility mode is an extension of that mechanism.67 - Shims do not work on 16-bit apps, kernel-driver dependencies, or direct hardware access. In particular, 16-bit apps cannot run on 64-bit Windows at all.8
- For apps that “require administrator but do not actually need it”, RunAsInvoker is the standard move.
__COMPAT_LAYER=RunAsInvokersuppresses the elevation request and lets the app run under standard privileges.9 - Running under a shim means you can extend life for now, but the real path is “make it run without a shim”. If you decide to extend life, record which shims make it run and manage that as material for a rewrite decision.
2. The Big Picture of Application Compatibility — The Backward-Compatibility Layers Windows Already Has
Before we talk about shims, here is a list of the mechanisms Windows already has for old apps. Even when people say “it started working in Compatibility mode”, what is actually saving the app is one of these layers, or a combination of several.
| Layer | What it does | Typical target |
|---|---|---|
| Shims (Compatibility mode) | Intercept API calls and spoof the same responses old Windows would have given | Apps written against an older OS in general |
| UAC virtualization (files / registry) | Redirect writes to HKLM\Software or Program Files that lack permission into a per-user VirtualStore |
32-bit apps written on the assumption of administrator privileges |
| WOW64 | Run 32-bit apps as-is on 64-bit Windows (provides 32-bit views of the registry and the file system) | 32-bit apps in general |
| DPI virtualization | Have a non-DPI-aware app draw at 96 DPI and display it by stretching the bitmap | Old apps on high-DPI displays |
UAC virtualization is a transitional measure that applies to 32-bit interactive processes that have no manifest, and Microsoft itself states that it is “a temporary technology that we intend to remove from a future version of Windows”.10 The real damage from Wow6432Node redirection and the VirtualStore, and how to deal with it, is covered in detail in “Registry 32-bit/64-bit Redirection and Virtualization Pitfalls”; this article keeps shims at the center and mentions the other layers only as far as needed.
flowchart TB
accTitle: Where UAC virtualization sits
accDescr: UAC virtualization is a transitional measure for 32-bit interactive processes that have no manifest; it redirects writes into a per-user VirtualStore, but Microsoft itself states that it is a temporary technology intended for removal from a future Windows
proc["32-bit interactive process with no manifest"] --> uacv["UAC virtualization applies"]
uacv --> vs["Redirected to a per-user VirtualStore"]
uacv -.-> tmp["Temporary technology intended for future removal"]
Figure 2: UAC virtualization is a transitional measure for 32-bit processes with no manifest, and you cannot rely on it permanently.
As a side note on DPI virtualization: an app that has not declared DPI awareness is treated as drawing at 96 DPI (100%), and Windows stretches the bitmap for display. That is why old apps look “blurry” on a high-DPI monitor, and the Compatibility tab’s “Override high DPI scaling behavior” is the switch that changes this virtualization behavior.11
flowchart TB
accTitle: How DPI virtualization works
accDescr: An app that has not declared DPI awareness is treated as drawing at 96 DPI; Windows stretches the bitmap so it looks blurry, and the Compatibility tab override of high-DPI settings switches this virtualization behavior
app["App that does not declare DPI awareness"] --> treat["Treated as drawing at 96 DPI"]
treat --> stretch["Bitmap is stretched for display"]
stretch --> blur["Looks blurry on a high-DPI monitor"]
tab["Override high DPI scaling behavior"] -.->|Switches the virtualization behavior| treat
Figure 3: A non-DPI-aware app is treated as 96 DPI and stretched; the Compatibility tab override is the switch for this virtualization.
3. What a Shim Really Is — Intercepting Between APIs by Rewriting the IAT
3.1. The “Interpreter” That Stands Between the App and the OS
A Windows executable (PE format) calls APIs in external DLLs through the import address table (IAT). When the app calls GetVersionEx, it is only jumping to the address written in the IAT. The shim mechanism exploits that. At load time it rewrites the IAT entry of the target API to the address of the shim code and inserts itself between the app and Windows. APIs obtained dynamically via GetProcAddress are handled by hooking GetProcAddress itself.3
Once it has intercepted, a shim might return an old version number to “what is the current OS version?”, or remap a file access to an unwritable location to somewhere else, and then call the real API if needed. From the app’s point of view it looks as if it is “running on old Windows”; from the OS’s point of view it looks as if “a well-behaved app is running” — the shim is the interpreter between the two.
flowchart TB
accTitle: The path by which a shim intercepts an API call
accDescr: An app's API call goes through the IAT; rewriting the IAT entry to the shim at load time lets the shim intercept, spoof the same response old Windows would have given, and then call the real API if needed
app["App"] -->|API call| iat["IAT entry"]
iat -->|Rewritten to the shim at load time| shim["Shim (interpreter)"]
shim -->|If needed| api["The real Windows API"]
shim -.-> lie["Spoofs the same response old Windows would have given"]
gpa["Calls via GetProcAddress"] -.->|Handled by a hook| shim
Figure 4: A shim intercepts between the app and the Windows API. What is rewritten is the app-side IAT; the OS itself does not change.
Three important properties follow from this design.3
- A shim runs as app-side code. It is not part of the OS, so it is subject to the same security constraints as the app. A shim cannot bypass the OS security mechanisms, and you do not need to loosen security settings in order to use a shim.
- What a shim can fix, an app-side code fix can also fix. A shim is a substitute for cases where “there is no source / we cannot fix it”; it is not more powerful than a code fix.
- User mode only. Compatibility problems in device drivers that run in kernel mode cannot be fixed by a shim.
3.2. The Shim Database (.sdb) and Matching
The correspondence table of “which shim to apply to which EXE” is the shim database, a binary file with the .sdb extension. Target app executables are registered in the database by attributes such as file name, size, checksum, and version (matching attributes), and they are matched at process start. Remedies include Appfix (a shim), which injects an API hook, and Apphelp, which displays a “this app has a compatibility problem” message. A bundle of several shims and flags is a compatibility layer (Compatibility mode).1
Easy to miss: this matching runs not only on apps that have Compatibility mode set, but on every process launch. Windows ships an OS-standard database of fixes for thousands of known apps (the files live under %WINDIR%\AppPatch), and on your PC today some old app is almost certainly starting with a shim attached without anyone noticing. Microsoft-provided compatibility fixes ship as part of Windows and are updated via Windows Update.3
flowchart TB
accTitle: Matching the shim database at process start
accDescr: Every process launch is matched against the shim database; if a registration matches the matching attributes, Appfix injects a shim or Apphelp displays a message, otherwise the process starts as-is
start["Process start"] --> db["Match against .sdb"]
db -.-> attr["File name, size, etc."]
db --> hit{"A registration?"}
hit -->|No| plain["Start as-is"]
hit -->|Yes| which{"Which kind?"}
which --> appfix["Appfix: inject shim"]
which --> apphelp["Apphelp: a message"]
layer["Compatibility layer"] -.-> appfix
layer -.-> layerN["Mode = shims + flags"]
Figure 5: Matching runs on every process launch, not only on apps that have Compatibility mode set.
3.3. PCA — The Mechanism That Applies Shims Automatically
Another path by which a shim can be applied without an administrator intending it is PCA (Program Compatibility Assistant). PCA watches app execution and, when it detects signs of a known compatibility problem, proposes applying a fix to the user or, in some cases, applies a compatibility setting automatically. For example, an app that crashes by calling code inside a freed DLL is assigned PINDLL, and an app that fails writing to a protected Windows file is assigned WRPMITIGATION.5
flowchart TB
accTitle: How PCA applies a compatibility setting automatically
accDescr: PCA watches app execution and, when it detects signs of a known compatibility problem, proposes applying a fix to the user or, in some cases, applies a compatibility setting automatically
run["App execution"] --> pca["PCA watches"]
pca --> sign{"Signs of a known problem?"}
sign -->|Yes| resp{"Which case?"}
resp -->|Handled by a proposal| suggest["Propose applying a fix"]
resp -->|Some cases| auto["Apply a compatibility setting automatically"]
sign -->|No| none["Run as-is"]
auto -.-> ex["Example: PINDLL or WRPMITIGATION"]
Figure 6: PCA watches app execution and, when it detects signs of a known problem, proposes a fix or applies one automatically.
The identity of “I never set anything, but at some point the Compatibility mode checkbox was on” is, in many cases, this. It is neither a fault nor a mis-click; it is Windows behaving as designed.
4. What the Representative Shims Can Do
From the ready-made shims Microsoft publishes, here is a selection that actually comes up often when extending the life of a business app.4
| Shim | What it can do (summary) |
|---|---|
| WinXPSP3VersionLie and other VersionLie-family shims | Return a specified older version to OS-version queries (version spoofing) |
| CorrectFilePaths | Remap access to an unwritable or nonexistent file path to another location |
| VirtualRegistry | Redirect or spoof registry reads and writes (including version spoofing and faking nonexistent keys) |
| ForceAdminAccess | Temporarily return True to a “are you a member of the Administrators group?” check |
| RunAsAdmin / RunAsHighest / RunAsInvoker | Give from the outside an execution level equivalent to requireAdministrator / highestAvailable / asInvoker in the manifest |
| WRPMitigation | Spoof success for writes to protected OS files and registry keys so the app can proceed |
| EmulateGetDiskFreeSpace | Report free disk space as a maximum of 2GB (for apps that overflow on large disks) |
| GlobalMemoryStatusLie | Spoof the reported memory-status values (for apps that fail a startup memory check) |
| LoadLibraryRedirect | Load Windows’ current DLL instead of an old system DLL the app ships |
Looking at the list, most shims are “a lie that returns the answer the old app expects”. The disk is at most 2GB, the OS is XP, you are an administrator — they recreate, inside that process only, the worldview of the era in which the app was born.
Version Spoofing Has Become “Official Default Behavior”
Version spoofing is not a special hack. From Windows 8.1 onward, the value GetVersionEx returns depends on the app’s manifest. An app with no <supportedOS> declaration in the manifest’s <compatibility> section is always given Windows 8-equivalent (6.2), whatever the actual OS is. When there is a declaration, the value up to the highest OS among those declared is returned (for example, if you have declared through the Windows 8.1 GUID, you get 6.3 even on Windows 11).67
So the “Windows version the app sees” is decided in these stacked stages.
- The value up to the OS declared in the manifest is returned (6.2 if there is no declaration)
- If Compatibility mode (a VersionLie-family shim) is applied, the version of the selected OS is returned6
flowchart TB
accTitle: How the OS version the app sees is decided
accDescr: The value GetVersionEx returns is decided by whether a supportedOS declaration is in the manifest; with no declaration Windows 8-equivalent 6.2 is returned, with a declaration the value up to the highest declared OS is returned, and if a VersionLie-family shim is applied it is overwritten with the selected OS version
q["GetVersionEx query"] --> m{"Is there a supportedOS declaration?"}
m -->|No| v62["Windows 8-equivalent (6.2) is returned"]
m -->|Yes| decl["Value up to the highest declared OS"]
v62 --> lie{"VersionLie-family shim applied?"}
decl --> lie
lie -->|Yes| fake["The OS value chosen in Compatibility mode"]
lie -->|No| asis["The value is returned as-is"]
Figure 7: The Windows version the app sees is decided in stacked stages by the manifest and by shims.
If an in-house app “branches on OS version and, confusingly, is judged as 8 even though this is Windows 11”, first suspect the supportedOS declaration in the manifest. Put the other way around, an old app that refuses to start based on a version check can, with high probability, be gotten through with a VersionLie shim. In many cases it is only looking at the version number, and the actual behavior is fine on a newer OS.
flowchart TB
accTitle: Two version-caused symptoms and how to deal with them
accDescr: If an in-house app is judged as 8 even though this is Windows 11, suspect the supportedOS declaration in the manifest; an old app that refuses to start on a version check can, with high probability, be gotten through with a VersionLie shim
sym1["Judged as 8 even though this is Windows 11"] --> fix1["Suspect the supportedOS declaration"]
sym2["Start refused on a version check"] --> fix2["Try getting through with VersionLie"]
fix2 -.-> why["The actual behavior is often fine on a newer OS"]
Figure 8: When the judgment is stale, suspect the manifest; when start is refused, suspect VersionLie.
5. What the Compatibility Mode Checkbox Does
Settings from Properties → the Compatibility tab are stored in the registry’s AppCompatFlags\Layers key. DXGI app-compatibility settings and the like use the same key as the place to specify a compatibility layer.2 Let’s actually look.
reg query "HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
For an EXE on which you set “Windows XP (Service Pack 3)”, “Run this program as an administrator”, and “Override high DPI scaling behavior” on the Compatibility tab, you will see a value such as the following.
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
C:\LegacyApp\Gyomu.exe REG_SZ ~ WINXPSP3 RUNASADMIN HIGHDPIAWARE
Representative correspondences between checkbox items and values (confirmed on Windows 11; item names and values can change by OS version).
| Compatibility tab item | Value written (example) | What it actually is |
|---|---|---|
| Compatibility mode: Windows XP (Service Pack 3) | WINXPSP3 | A compatibility layer that bundles version spoofing and several other shims |
| Reduced color mode (8-bit / 256 colors) | 256COLOR | Relaxation for the old color mode |
| Run in 640 × 480 screen resolution | 640X480 | Run at a low resolution |
| Disable fullscreen optimizations | DISABLEDXMAXIMIZEDWINDOWEDMODE | Disable drawing optimizations when fullscreen |
| Override high DPI scaling behavior (Application) | HIGHDPIAWARE | Stop DPI virtualization (bitmap stretching)11 |
| Run this program as an administrator | RUNASADMIN | Request elevation at start |
Three points to keep.
- “Run this program as an administrator” is written in the same place. Compatibility mode and the elevation flag live together in the same Layers key, and that is where the confusion “I set Compatibility mode and elevation came along / disappeared” is born. Looking at the value directly separates the two.
- What is written in HKCU is “that user’s setting”. If you set it from the tab’s “Change settings for all users”, it is written to the same-named key on the HKLM side and applies to all users. When you distribute it in imaging, be conscious of which side you are writing to.
- The checkbox is only the entrance to the ready-made layers. The tab lets you pick representative layers only; you cannot pick individual shims and combine them. That is what Compatibility Administrator in the next chapter does.
flowchart TB
accTitle: The path from a Compatibility tab setting to taking effect
accDescr: Compatibility tab settings are stored as an EXE path and a value in the AppCompatFlags Layers key; the next time that EXE starts, the loader reads the value and applies the corresponding compatibility layer to the process
tab["Set on the Compatibility tab"] --> reg["Store the EXE path and value in the Layers key"]
reg --> boot["Next EXE start"]
boot --> loader["The loader reads the value"]
loader --> apply["Apply the compatibility layer to the process"]
reg -.-> hkcu["HKCU is that user only"]
reg -.-> hklm["HKLM applies to all users"]
Figure 9: The checkbox is in reality a write to the Layers key, and application happens at the next start.
6. Compatibility Administrator in Practice — Building and Distributing a Custom .sdb
6.1. How to Get It, and Caveats
Compatibility Administrator is a tool included in the Windows ADK (Windows Assessment and Deployment Kit).12 After install, both 32-bit and 64-bit editions are present, and you must use the 32-bit edition for 32-bit apps and the 64-bit edition for 64-bit apps.13
There is another important caveat. If you start Compatibility Administrator elevated (as administrator) and test, UAC virtualization and redirection do not behave as they would for a real user, and you can mis-judge that “it is fixed”. Always confirm the effect of a fix with the same account and privileges as the actual user.4
flowchart TB
accTitle: Two caveats when using Compatibility Administrator
accDescr: Use the 32-bit edition for 32-bit apps and the 64-bit edition for 64-bit apps, and confirm the effect of a fix with the same account and privileges as the actual user, not in an elevated state
bit{"App bitness?"}
bit -->|32-bit| tool32["Use 32-bit edition"]
bit -->|64-bit| tool64["Use 64-bit edition"]
priv{"Test privileges?"}
priv -->|Elevated| wrong["May mis-judge a fix"]
priv -->|Same as user| ok["Confirm the effect"]
tool32 ~~~ priv
Figure 10: Choosing the 32-bit or 64-bit edition, and confirming with the same privileges as the actual user, are the caveats at the entrance.
6.2. Procedure for Building a Custom Compatibility Database
The outline is as follows.14
- In Compatibility Administrator’s left pane, create a new database under “Custom Databases” and choose “Create New” → “Application Fix”
- Enter the app name and vendor name, and specify the target EXE file
- Choose the compatibility mode (layer) to apply — trying a bundle such as “Windows XP compatibility” first is the short path
- If needed, add individual compatibility fixes (shims) — you can narrow it to a minimal set such as VersionLie only or CorrectFilePaths only
- Confirm the matching conditions (file size, checksum, version, and so on) and save
Matching conditions are the key to “apply this only to this EXE”. The default basic conditions are usually enough, but we recommend leaving a condition that can identify the app version. That prevents the accident of an old lie continuing to apply to a new version when the vendor later ships a fixed edition.1415
flowchart TB
accTitle: Procedure for building a custom compatibility database
accDescr: Create an Application Fix in a new database, specify the app name and target EXE, try a compatibility-mode bundle first and then narrow to individual shims if needed, confirm matching conditions, and save
new["Create a new database"] --> fix["Choose Application Fix"]
fix --> info["Specify the app name and target EXE"]
info --> layer["Try a compatibility-mode bundle"]
layer --> single["If needed, narrow to individual shims"]
single --> match["Confirm matching conditions and save"]
match -.-> ver["Leave a condition that identifies the version"]
Figure 11: For an Application Fix, try a compatibility-mode bundle first, narrow to a minimal set, and limit the target with matching conditions.
Test the .sdb you created on a validation machine first. Once it works as intended, you roll it out to the organization.
6.3. Distributing with sdbinst
The command that applies a custom .sdb to each PC is sdbinst.exe (requires administrator privileges).15
:: Install (-q is silent with no confirmation)
sdbinst -q "C:\Deploy\MyCorpFixes.sdb"
:: Uninstall (by file)
sdbinst -q -u "C:\Deploy\MyCorpFixes.sdb"
:: Uninstall (by database GUID)
sdbinst -q -u -g {database GUID}
As an organizational-deployment strategy, Microsoft recommends consolidating into one company-wide (or per-department) custom database and managing it centrally, rather than shipping a separate .sdb with each app’s installer. The more fixes you have, the easier it is to update and redistribute one database than to distribute many one-line databases. A custom database has its own GUID, and installing a new version with the same GUID automatically replaces the old version, so update operations stay simple as well. Put the distribution itself on an existing path that can run with administrator privileges, such as packaging as an MSI or a startup script.15
flowchart TB
accTitle: The path from creating a custom .sdb to distributing it
accDescr: Create a custom compatibility database in Compatibility Administrator, test it on a validation machine, apply it to each PC with sdbinst, and on update install a new version with the same GUID so the old version is replaced automatically
make["Create in Compatibility Administrator"] --> test["Test on a validation machine"]
test --> deploy["Apply to each PC with sdbinst"]
deploy --> update["Install a new version with the same GUID"]
update -.-> replace["The old version is replaced automatically"]
deploy -.-> inv["Registered in Programs and Features"]
Figure 12: A custom .sdb is rolled out through create, validate, and sdbinst distribution, and updates are managed by GUID.
An installed custom database is registered as an item in “Programs and Features (Installed apps)”, so you can also confirm inventory and removal from there. Which PCs have which .sdb is information that belongs on the asset-management ledger.
7. Cases Where It Does Not Work, and the Limits
Shims are not a silver bullet. By design they do not work in the following cases.
- Kernel-mode problems. A shim runs inside a user-mode process, so a device-driver incompatibility cannot be fixed. If the driver for an old measuring instrument, USB dongle, or printer does not support Windows 11, nothing you apply on the app side will solve it. Code that runs in the kernel, such as parts of antivirus software, is the same.3
- 16-bit apps. 64-bit Windows does not support running 16-bit apps. Handles have 32 valid bits on 64-bit Windows and cannot be truncated to pass to a 16-bit app, so start fails with
ERROR_BAD_EXE_FORMAT.8 Even when the app itself is 32-bit, packages from that era whose installer stub is 16-bit exist, and they show up as “the app would run, but we cannot install it”. - Direct hardware access. Industrial apps that assume they can touch I/O ports or physical memory directly are not permitted to do so from user mode on modern Windows in the first place, and that is beyond the range a shim can spoof.
- Bypassing security mechanisms. Because a shim runs under the same security constraints as the app, it cannot make “something you cannot do for lack of privilege” possible. ForceAdminAccess and WRPMitigation only spoof success of a check or a write so the app can proceed; they are not actually rewriting a protected resource.34
- Apps that check their own integrity. Apps with old copy protection or tamper detection can treat the API hook itself as abnormal and stop working.
flowchart TB
accTitle: Cases where a shim does not work
accDescr: A shim runs inside a user-mode process, so it does not work on kernel-mode driver problems, 16-bit apps, direct hardware access, or bypassing security mechanisms
shim["Shim (runs in user mode)"] -->|Does not work| drv["Kernel driver"]
shim -->|Does not work| b16["16-bit app"]
shim -->|Does not work| hw["Direct hardware access"]
shim -->|Does not work| sec["Bypassing security mechanisms"]
b16 -.-> fmt["On 64-bit, start itself fails"]
sec -.-> fake["Only spoofs success so the app can proceed"]
Figure 13: A shim is user-mode only and does not reach the kernel, 16-bit apps, direct hardware access, or security bypass.
And the essential limit common to every shim is that it is a stopgap. A shim is a lie tailored to a particular use of a particular API, and if the OS-side implementation changes, the assumption collapses. Microsoft-provided shims are maintained as part of Windows via Windows Update,3 but looking after the lies you applied with a custom database is your organization’s job. Budget, as a cost of life-extension, an operation that validates the “list of apps being kept alive by shims” on every feature update.
flowchart TB
accTitle: Shims as a stopgap, and who is responsible for maintaining them
accDescr: A shim is a lie tailored to a particular API use and the assumption collapses if the OS-side implementation changes; Microsoft-provided shims are maintained via Windows Update, but looking after lies applied with a custom database is your organization's job, and validation on every feature update is a cost of life-extension
shim["Shim = a stopgap lie"] --> break["OS change breaks it"]
ms["Microsoft shims"] --> wu["Via Windows Update"]
own["Custom-database lies"] --> self["Org looks after them"]
self --> cost["Validate each update"]
cost -.-> costN["Life-extension cost"]
break ~~~ ms
wu ~~~ own
Figure 14: Responsibility for maintaining the shim lie is split between the Microsoft-provided set and your organization’s custom set.
8. The Practical Value of RunAsInvoker — Silencing Only the Elevation Request
Among shims, the one that comes up most in day-to-day IT work is RunAsInvoker.
Some old business apps declare requireAdministrator in the manifest, or are mis-detected as an installer from the EXE name or contents, and request UAC elevation every time they start. Many of them, though, only ask for administrator out of XP-era inertia and do not actually use administrator privileges. Applying the RunAsInvoker shim overwrites both installer detection and the manifest, and the app starts with the token inherited from the parent process (= standard-user privileges).9
flowchart TB
accTitle: How RunAsInvoker suppresses an elevation request
accDescr: A requireAdministrator declaration in the manifest or mis-detection as an installer causes a UAC elevation request at start, but applying RunAsInvoker overwrites both and the app starts with the token inherited from the parent
manifest["requireAdministrator declaration"] --> shim{"RunAsInvoker applied?"}
detect["Mis-detected as an installer"] --> shim
shim -->|No| uac["UAC elevation request on every start"]
shim -->|Yes| token["Starts with the parent's token"]
token -.-> limit["Work that truly needs administrator fails inside the app"]
Figure 15: RunAsInvoker only overwrites the cause of the elevation request; privileges do not increase.
Even without building a .sdb in Compatibility Administrator, you can apply the same layer temporarily with the __COMPAT_LAYER environment variable.
:: Apply RunAsInvoker to child processes started from this command prompt
set __COMPAT_LAYER=RunAsInvoker
start "" "C:\LegacyApp\Gyomu.exe"
# In PowerShell
$env:__COMPAT_LAYER = 'RunAsInvoker'
Start-Process 'C:\LegacyApp\Gyomu.exe'
If you turn those two lines into a batch file and distribute it as a shortcut, you can avoid handing local administrator privileges to standard users, and IT is no longer called every time for a UAC password. It is a compatibility technique that hardens the defense, in line with least privilege.
flowchart TB
accTitle: The effect of distributing a RunAsInvoker batch
accDescr: Distributing a two-line batch that sets RunAsInvoker as a shortcut means you can avoid handing local administrator privileges to standard users, IT is no longer called for a UAC password, and the operation follows least privilege
bat["Distribute a two-line batch"] --> noadmin["You can avoid handing out administrator privileges"]
bat --> nocall["IT is not called for UAC"]
noadmin --> lp["An operation that follows least privilege"]
nocall --> lp
Figure 16: Distributing a batch alone can reduce both handing out administrator privileges and being called for UAC.
The caveats as well, clearly.
- Privileges do not increase. Work that truly needs administrator privileges (writes to HKLM, updates under Program Files, and so on) will error inside the app, or, if the conditions are met, will be redirected to the VirtualStore by UAC virtualization.10 If saving settings suddenly “stopped working”, suspect virtualization.
- The environment-variable method applies only to child processes. For a permanent application, a direct setting in the Layers key (RUNASINVOKER has no item on the tab) or distribution via a .sdb is reliable.
- Fixing the write destination is the real path. If you can change the app, move the settings file under
%APPDATA%and declareasInvokerin the manifest — that is the correct shape.9
flowchart TB
accTitle: Temporary and permanent application of RunAsInvoker
accDescr: Application via the COMPAT_LAYER environment variable applies only to child processes started from there; for a permanent application use a direct setting in the Layers key or distribution via a .sdb
env["Set via an environment variable"] --> child["Applies only to child processes"]
child -.-> tmp["Temporary application"]
layers["Set directly in the Layers key"] --> always["Permanent application"]
sdb["Distribute via an sdb"] --> always
Figure 17: The environment-variable method is a temporary application limited to child processes; making it permanent is done with the Layers key or a .sdb.
9. Deciding Between Life-Extension and Migration — What to Think After It Works Under a Shim
The moment it works under a shim is a relief, but it is important not to stop thinking there. Working under a shim only means it happened to fit a receptacle Windows prepared. The axes of the decision, in a table.
| Decision axis | Conditions that lean toward life-extension (shim) | Conditions that lean toward migration / rewrite |
|---|---|---|
| Remaining period of use | Planned to retire with the business in 1–2 years | Assumed to keep using for 5 years or more |
| Source code | None (vendor gone, or lost) | Exists, or the asset can be recovered |
| Depth of dependency | A user-mode API-compatibility problem only | Depends on a driver, 16-bit, or dedicated hardware |
| Alternatives | No packaged product or new version exists | The destination product and technology are clear |
| Impact when it fails | The business can run on a fallback procedure | Core business takes a direct hit |
| Validation capacity | You can confirm behavior on every feature update | No validation resource, and it tends to be frozen |
If you decide on life-extension, put the following three points into the operation as a set.
- Record. Which EXE, which shim/layer, and why. Leave the Layers-key value and the .sdb GUID in a ledger. “Nobody knows why it works” is the largest debt you leave the next person. This is the same preservation mindset covered in “When You Inherit a System With No Source Code and No Documentation”.
- Validate. Include start and the main operations of apps being kept alive by shims in the validation items for a Windows feature update. Tie it to the OS-replacement plan as well (Practical Options After Windows 10 End of Support).
- Set a deadline. Decide the end of life-extension — “until the next core-system refresh”, “until March 2028” — and run migration consideration in parallel.
flowchart TB
accTitle: The three-point operating set once you decide on life-extension
accDescr: Record in a ledger which shim makes it run, validate the behavior of apps being kept alive by shims on every feature update, set a deadline for the end of life-extension, and run migration consideration in parallel
decide["Decide on life-extension"] --> rec["Record: which shim makes it run, into a ledger"]
rec --> verify["Validate: confirm behavior on every feature update"]
verify --> deadline["Deadline: decide the end of life-extension"]
deadline --> mig["Run migration consideration in parallel"]
Figure 18: Life-extension is operated as a three-point set of record, validate, and deadline, including running migration consideration in parallel.
On the migration side, the standard options change with the app’s technology. For VB6, the three-way choice of full rewrite, automatic conversion, and staged migration organized in “How Long Will VB6 Apps Keep Running?”; for a dependency on ActiveX/OCX, the keep / wrap / replace decision table in “How to Handle ActiveX / OCX Today”. The healthy way to position a shim is as buying time so that the consideration and preparation period of that migration project can run safely.
flowchart TB
accTitle: Options on the migration side, and where a shim sits
accDescr: The standard migration options change with the app's technology; for VB6 the three-way choice is rewrite, automatic conversion, and staged migration, for an ActiveX dependency the keep / wrap / replace decision table applies, and a shim is positioned as buying time for the consideration and preparation of the migration project
tech{"What is the app's technology?"} -->|VB6| vb["Rewrite, automatic conversion, or staged migration"]
tech -->|ActiveX dependency| ax["Keep, wrap, or replace"]
shim["Life-extension via a shim"] -.->|Buys time for consideration and preparation| tech
Figure 19: The standard migration options are decided by the app’s technology, and a shim is positioned as buying time for that consideration.
10. Summary
- The real identity of Compatibility mode is shims. Settings from the Compatibility tab are written to the AppCompatFlags\Layers key and injected into the process at start as an API hook via IAT rewrite.
- Shims are a collection of “lies that return the answer the old app expects”. Ready-made shims for version spoofing, path remapping, registry spoofing, spoofing of administrator checks, and the like are provided.
- Windows itself uses a large number of shims by default, and PCA can apply them automatically. Relying on Compatibility mode is itself a reasonable choice that rides an official OS mechanism.
- There is a principled limit of user-mode only and no security bypass, and kernel drivers, 16-bit apps, and direct hardware access cannot be saved.
- Organizational rollout is building a custom .sdb in Compatibility Administrator (Windows ADK) and distributing it with sdbinst. Choosing the 32-bit/64-bit edition, testing with the actual user account, and managing updates by GUID are the practical points.
- Apps that “require administrator but do not actually need it” can be brought down to standard privileges with
__COMPAT_LAYER=RunAsInvoker. It is a defensive technique that silences the elevation request rather than handing out privileges. - Running under a shim is life-extension, not a solution. Recording what makes it run, validating on every feature update, and setting a deadline so that migration runs in parallel — that three-point set is included in the decision to “rely on Compatibility mode”.
The next time an old app starts working after a Compatibility mode checkbox, ask this again. “Which lie is this app running thanks to? How long will that lie keep working?” If you can answer, life-extension is a respectable strategy.
Related Articles
- Registry 32-bit/64-bit Redirection and Virtualization Pitfalls — Wow6432Node and the “The Value I Wrote Isn’t There” Problem
- How Long Will VB6 Apps Keep Running? — Runtime Support Status and a Practical Path to .NET Migration
- How to Handle ActiveX / OCX Today - A Keep / Wrap / Replace Decision Table
- Practical Options After Windows 10 End of Support — A Decision Table for ESU, LTSC, and Replacement
- When You Inherit a System With No Source Code and No Documentation — A Practical Playbook for Keeping It Running
- Windows Shell Integration Today — Context Menus, File Associations, and What Changed in Windows 11
Related Consulting Areas
KomuraSoft LLC handles investigation of how old business apps with no source code behave and designing their life-extension (selecting shims and Compatibility mode, building and rolling out a custom .sdb), compatibility validation of existing apps for a Windows 11 migration, and planning a rewrite or migration that runs in parallel with life-extension. Consultation from the stage of “it started working in Compatibility mode, but is it all right to leave it this way?” is fine.
- Legacy Asset Migration
- Windows Application Development
- Technical Consulting & Design Review
- Contact Us
References
-
Microsoft Learn, Application Compatibility Database. That the compatibility infrastructure manages problems and remedies in a .sdb-format database, matching by executable attributes, Apphelp (displaying a message) and Appfix (an API hook via a shim), and a compatibility layer (mode) that bundles several shims and flags. ↩ ↩2 ↩3
-
Microsoft Learn, DXGI overview. That application-compatibility settings are stored in the registry key HKCU\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers (using DXGI compatibility settings as the example). ↩ ↩2
-
Microsoft Learn, Understanding and Using Compatibility Fixes. That a compatibility fix (shim) redirects API calls by rewriting the IAT (import address table), that dynamic linking is handled by hooking GetProcAddress, that a shim is subject to the same security constraints as the app and cannot bypass OS security mechanisms, that it is user-mode only and cannot fix driver problems, that a fix possible with a shim is also possible with a code fix, usage scenarios such as apps whose vendor support has ended, and that Microsoft-provided compatibility fixes ship as part of Windows and are updated via Windows Update. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Compatibility Fixes for Windows 10, Windows 8, Windows 7, and Windows Vista. A list and description of known compatibility fixes including CorrectFilePaths, VirtualRegistry, ForceAdminAccess, RunAsAdmin/RunAsHighest/RunAsInvoker, WRPMitigation, EmulateGetDiskFreeSpace, GlobalMemoryStatusLie, LoadLibraryRedirect, and the VersionLie family; choosing the 32-bit/64-bit edition of Compatibility Administrator; and that testing in an elevated state means virtualization and redirection do not behave as expected, so you should validate with the actual user account. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Program Compatibility Assistant scenarios for Windows 8. That PCA watches app execution, detects signs of a known compatibility problem, and proposes applying a recommended fix or applies it automatically (PINDLL, DISABLEUSERCALLBACKEXCEPTION, VIRTUALIZEDELETE, WRPMITIGATION, and the like), and applying a fix from the Compatibility tab and the Program Compatibility Troubleshooter. ↩ ↩2
-
Microsoft Learn, GetVersionExW function. That from Windows 8.1 onward the value GetVersionEx returns depends on the manifest, that an app not manifested for Windows 8.1/10 is given the Windows 8 version value (6.2), and that when Compatibility mode is enabled the version of the selected OS is reported. ↩ ↩2 ↩3
-
Microsoft Learn, Targeting your application for Windows. How to declare supported-OS GUIDs with a supportedOS element in the compatibility section of the app manifest, the behavior when there is no declaration, and that a 32-bit x86 app that does not include trustInfo is subject to UAC file virtualization (write redirection to the VirtualStore). ↩ ↩2
-
Microsoft Learn, Running 32-bit Applications. That WOW64 is an emulation layer that runs 32-bit apps on 64-bit Windows and isolates file and registry collisions, and that 64-bit Windows does not support running 16-bit apps, with start failing with ERROR_BAD_EXE_FORMAT because of the number of valid bits in a handle. ↩ ↩2
-
Microsoft Learn, Using the RunAsInvoker Fix. That the RunAsInvoker compatibility fix starts the app with the token inherited from the parent process, that it overwrites both installer detection and manifest processing, that it is applied as a loader flag without intercepting APIs, and that when you can fix the code the proper fix is to declare asInvoker in the manifest. ↩ ↩2 ↩3
-
Microsoft Learn, Registry Virtualization. That registry virtualization is a compatibility technology that transparently redirects global writes to HKLM\Software into a per-user VirtualStore, that only 32-bit interactive processes are in scope and it is disabled for processes that specify requestedExecutionLevel in the manifest and for 64-bit processes, and that it is positioned as a temporary technology intended for removal from a future Windows. ↩ ↩2
-
Microsoft Learn, High DPI Desktop Application Development on Windows. That a non-DPI-aware app is treated as drawing at a fixed 96 DPI and, on a high-DPI display, Windows stretches the bitmap so it looks blurry, and the differences among DPI-awareness modes (Unaware/System/Per-Monitor). ↩ ↩2
-
Microsoft Learn, Download and install the Windows ADK. That the Windows ADK includes Compatibility Administrator and Standard User Analyzer, and how to think about choosing an ADK version and how to download and install it. ↩
-
Microsoft Learn, Compatibility Administrator User’s Guide. That Compatibility Administrator provides applying compatibility fixes, compatibility modes, and AppHelp messages and creating a custom database, and that both 32-bit and 64-bit editions are installed and you must use the 32-bit edition for 32-bit apps and the 64-bit edition for 64-bit apps. ↩
-
Microsoft Learn, Creating a Custom Compatibility Fix in Compatibility Administrator. That a compatibility fix (formerly called a shim) is a small piece of code that intercepts an API call; the procedure for creating an Application Fix in a custom database (specifying the app name, vendor, and target EXE, choosing a compatibility mode, choosing additional shims, setting matching conditions); and that you should leave conditions that correctly identify the app while narrowing the matching information. ↩ ↩2
-
Microsoft Learn, Compatibility Fix Database Management Strategies and Deployment. That a centrally managed database is recommended as the management strategy for a custom compatibility database, that a compatibility fix should include a version check (matching condition) so it is not applied to a new version, local install with Sdbinst.exe (-q, -u, -g options), that installing a new version with the same database GUID automatically uninstalls the old version, and distribution methods via MSI or a script. ↩ ↩2 ↩3
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Windows App Outsourcing and Contract Development: What to Sort Out Before You Ask
Before commissioning Windows app outsourcing or contract development, here is how to sort out existing software modification, device inte...
The Win32 Thread Pool API — Concurrency Without Creating Threads, via CreateThreadpoolWork
Are you spawning CreateThread calls all over your native code? This article explains the Win32 thread pool API redesigned in Vista — the ...
Named Pipes in Practice — Windows' Standard IPC from Design to Security
A practical guide to named pipes, Windows' standard inter-process communication. This article organises, from primary sources, the choice...
Apps That Break on Resume from Sleep — How Windows Power Events Work and How to Build Business Apps That Survive Them
You opened the laptop and the business app's connections were dead — the cause is a design that never accounted for sleep. This article c...
DllMain and the Loader Lock — The Real Reason You're Told to "Do Nothing in DLL Initialization"
Why you must not call LoadLibrary or synchronize with other threads from DllMain. Drawing on primary sources, this article explains how t...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Frequently Asked Questions
Common questions about the topic of this article.
- If an app started working after I checked Compatibility mode, is it all right to keep using it that way?
- For keeping the business running in the short term, yes. Compatibility mode is a user-mode API hook called a shim, and it is an official OS-provided mechanism. A shim is still a stopgap for making an app run without fixing it, though, and another OS update can change the assumptions and break it again. Record the fact that it runs under Compatibility mode in a ledger, and treat that record as part of the decision to rewrite the app or to extend its life on purpose.
- What does the Compatibility mode checkbox actually do?
- When you save settings on the Compatibility tab of the properties dialog, Windows writes the path of the target EXE and a value such as "WINXPSP3" or "HIGHDPIAWARE" under the HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers key. The next time that EXE starts, the Windows loader reads the value and applies the corresponding compatibility layer (a bundle of shims) to the process. Windows XP compatibility mode, for example, spoofs the OS version by returning an old value from version-query APIs. The OS itself is not being changed; only that process is being shown a pretend older Windows.
- Can 16-bit-era apps be run under Compatibility mode on 64-bit Windows?
- No. 64-bit Windows runs 32-bit apps through WOW64, but it does not support running 16-bit apps, and an attempt to start one fails with ERROR_BAD_EXE_FORMAT. That is an architectural limit a shim cannot work around. Older packages whose installer stub is 16-bit fail for the same reason. If you truly need them, you have to look outside Compatibility mode — for example a virtual machine that includes 32-bit Windows.
- Can I run an app that "will not start unless it is run as administrator" under a standard user account?
- RunAsInvoker is worth trying. If you run set __COMPAT_LAYER=RunAsInvoker at a command prompt and then start the app, elevation requests from a requireAdministrator manifest or from installer detection are suppressed, and the app starts with the same (standard-user) privileges as the caller. For an app that only asks for administrator rights and never actually uses them, this alone can take elevation out of day-to-day operation. Privileges do not increase, so work that truly needs administrator rights will fail inside the app. Adopt it only after you have verified the behavior.
- Where do I get Compatibility Administrator?
- It is included in the Windows ADK (Windows Assessment and Deployment Kit). Download the ADK from Microsoft's site and, at install time, select the Application Compatibility Tools features. Both 32-bit and 64-bit editions are installed; you must use the 32-bit edition for 32-bit apps and the 64-bit edition for 64-bit apps. Apply a custom compatibility database (.sdb) you create by running the sdbinst command on each PC.