How Windows App Compatibility Works — Keeping Old Apps Alive with Compatibility Mode, Shims, and Compatibility Administrator
· Updated: · Go Komura · Windows, Compatibility Mode, Shims, Application Compatibility, Compatibility Administrator, Legacy Asset Reuse, Windows Development, Existing Systems
Revision history (first version, published Aug 20, 2026)
- First published
Cite this article(DOI (registered archive): 10.5281/zenodo.22170873)
The DOIs below refer to previously archived versions and may not match the current text. Use this page’s URL to reference the current text.
Go Komura (2026). How Windows App Compatibility Works — Keeping Old Apps Alive with Compatibility Mode, Shims, and Compatibility Administrator. KomuraSoft LLC. https://comcomponent.com/en/blog/windows-appcompat-shims-compatibility-mode/
- DOI (registered archive)
- 10.5281/zenodo.22170873
- DOI (last registered version)
- 10.5281/zenodo.22170874
“A ten-year-old business app whose source code no longer exists will not start on a new Windows 11 PC. But when I select ‘Windows XP’ on the Compatibility tab, it runs. Is it all right to keep using it this way?” Teams that look after old business apps bring us questions like this.
Compatibility mode is not a feature that turns the whole OS back into an older Windows. It is a mechanism that supplements, for that app alone, the behavior the app expects from an older Windows. At its center are small compatibility fixes that intercept calls between the app and the Windows API: shims.1
This article is written for IT staff at small and mid-sized companies and for Windows app developers. It works through the topic in the order what can be fixed → why it runs → how to apply and distribute it → how to decide between life-extension and migration, using primary sources on Microsoft Learn to confirm what happens beyond the checkbox.
1. The Conclusion First — Compatibility Mode Is Fine to Use, but Manage It as Life-Extension
Keeping the business running on Compatibility mode for the time being is a reasonable choice in itself; it uses a mechanism that Windows provides officially. Windows itself applies compatibility fixes to known apps.1 The app has not been fixed, though. The real goal is eventually to fix it so that it runs without shims.
Whether to use it is easiest to decide in the following order.
| Order of decision | What to check | Where to read |
|---|---|---|
| 1. Determine whether it is in scope | Is the problem in how the app uses the API? Or is it a driver, 16-bit, or dedicated-hardware problem? | Sections 2 and 3 |
| 2. Choose the fix you need | What has to be supplemented for it to run: the version check, paths, privilege requests, and so on | Sections 4 and 5. Ready-made settings in Section 6, RunAsInvoker in Section 7, distributing individual shims in Section 8 |
| 3. Decide how to operate it once it runs | Can you record the settings and verify them at each Windows update? How long will you extend its life? | Section 9 |
In particular, returning success to an “is this an administrator?” check and granting administrator privileges are two different things. A shim is subject to the same security constraints as the app and does not bypass the OS’s protections.12 RunAsInvoker, likewise, suppresses the elevation request and starts the app with the same privileges as the caller; it is not a feature that adds privileges.3
Once you understand the mechanism, you can turn “we cannot touch it because nobody knows why it works” into life-extension where you can explain how far you can rely on it, under what conditions it breaks, and when to rewrite.
flowchart TB
accTitle: Understanding the mechanism changes the quality of life-extension
accDescr: Using Compatibility mode without knowing the mechanism leads to unstable life-extension nobody dares touch, while 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 it without knowing the mechanism"] --> fear["Unstable life-extension nobody dares touch"]
known["Use it after understanding the mechanism"] --> judge["Decisions backed by reasons"]
judge -.-> j1["How far you can rely on it"]
judge -.-> j2["What will break it"]
judge -.-> j3["When you should rewrite"]
Figure 1: The same life-extension differs in quality between unease without knowing the mechanism and decisions grounded in understanding.
In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (16 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle
2. The First Triage — Is It a Problem a Shim Can Fix?
2.1. What Can Be Fixed Is a User-Mode Problem on the App Side
What a shim can do stays within the range that a code fix in the app could also achieve. It is an alternative for when the source code is gone, the vendor’s support has ended, or the app cannot be fixed right now; it is not more powerful than fixing the code.1
Problems such as refusing to start after looking at the OS version, assuming an old write location, or requesting administrator privileges that are not actually needed are candidates. Section 5 looks at the specific shims.
2.2. Cases That No Amount of Trying Settings Will Solve
The following problems need a remedy other than Compatibility mode.
| Problem | Why a shim cannot solve it |
|---|---|
| Kernel-mode incompatibility | A shim runs inside a user-mode process, so it cannot fix a device driver. The same applies to unsupported drivers for old measuring instruments, USB dongles, and printers, and to the parts of antivirus software that run in the kernel |
| 16-bit apps on 64-bit Windows | The OS does not support running 16-bit apps, so startup itself fails |
| Direct hardware access | An app that assumes it can touch I/O ports or physical memory directly from user mode is beyond what a shim can fake |
| Bypassing security mechanisms | A shim cannot turn an operation the app is not allowed to perform into a permitted one |
The kernel-mode problems and the security constraints are limits that come from where a shim runs.1 ForceAdminAccess and WRPMitigation only fake the success of a check or a write so that the app can proceed; they do not actually modify the protected resource.2
For the 16-bit problem, check the installer as well as the app itself. Even if the app is 32-bit, an old package whose launcher (stub) alone is 16-bit ends up as “the app runs but cannot be installed.” On 64-bit Windows, handles have 32 significant bits and cannot be truncated to 16 bits when passed, among other reasons, so 16-bit apps are not supported and startup fails with ERROR_BAD_EXE_FORMAT.4
flowchart TB
accTitle: Cases where shims do 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)"] -->|no effect| drv["Kernel driver"]
shim -->|no effect| b16["16-bit app"]
shim -->|no effect| hw["Direct hardware access"]
shim -->|no effect| sec["Bypassing security mechanisms"]
b16 -.-> fmt["Startup itself fails on 64-bit"]
sec -.-> fake["Only fakes success to let the app proceed"]
Figure 2: Shims are limited to user mode and do not reach the kernel, 16-bit apps, direct hardware access, or security bypasses.
Also, an app with self-integrity checks, such as old copy protection or tamper detection, may treat the API hook itself as an anomaly. It is not the case that every user-mode app can be rescued by a shim.
3. Telling Similar Features Apart — Shims, UAC Virtualization, WOW64, and DPI Virtualization
When “the old app ran,” the mechanism at work is not necessarily just a shim. Let us separate the backward-compatibility layers Windows has. In practice, several of them may be combined.
| Layer | What it does | Main targets |
|---|---|---|
| Shims (Compatibility mode) | Intercept API calls and fake the same responses an older Windows would give | Apps in general written against an older OS |
| UAC virtualization (file/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 they are on 64-bit Windows (provides 32-bit views of the registry and files) | 32-bit apps in general |
| DPI virtualization | Have a non-DPI-aware app render at 96 DPI and display it with bitmap stretching | Old apps on high-DPI displays |
3.1. UAC Virtualization: Diverting Writes to a Per-User Location
UAC virtualization is a transitional measure that works on 32-bit interactive processes without a manifest. Microsoft itself positions it as an interim technology that it intends to remove from a future Windows.5 WOW64, which runs 32-bit apps, and UAC virtualization, which redirects writes to per-user locations, are separate mechanisms.
Redirection to Wow6432Node and the real-world harm and remedies of VirtualStore are covered in Registry 32-bit/64-bit Redirection and Virtualization Pitfalls. This article stays within what is needed to distinguish them from shims.
flowchart TB
accTitle: Where UAC virtualization fits
accDescr: UAC virtualization is a transitional measure that works on 32-bit interactive processes without a manifest and redirects writes to a per-user VirtualStore, but Microsoft itself states that it is an interim technology it intends to remove from a future Windows
proc["32-bit interactive process without a manifest"] --> uacv["UAC virtualization takes effect"]
uacv --> vs["Writes redirected to a per-user VirtualStore"]
uacv -.-> tmp["Interim technology slated for removal"]
Figure 3: UAC virtualization is a transitional measure for 32-bit processes without a manifest and cannot be relied on permanently.
3.2. DPI Virtualization: Stretching Old Rendering
An app that does not declare DPI awareness is treated as rendering at 96 DPI (100%). Because Windows stretches that bitmap, the display looks blurry on a high-DPI monitor. “Override high DPI scaling behavior” on the Compatibility tab is the switch that changes the behavior of this virtualization.6
flowchart TB
accTitle: How DPI virtualization works
accDescr: An app that does not declare DPI awareness is treated as rendering at 96 DPI and Windows stretches the bitmap for display, so it looks blurry, and Override high DPI scaling behavior on the Compatibility tab switches the behavior of this virtualization
app["App that does not declare DPI awareness"] --> treat["Treated as rendering at 96 DPI"]
treat --> stretch["Bitmap stretched for display"]
stretch --> blur["Blurry on a high-DPI monitor"]
tab["Override high DPI scaling behavior"] -.->|switches the virtualization behavior| treat
Figure 4: A non-DPI-aware app is treated as 96 DPI and stretched, and the override on the Compatibility tab is the switch for this virtualization.
4. Why Compatibility Mode Makes It Run — Shims and Their Application at Startup
4.1. Replacing the Destination of API Calls
The path by which a Windows executable (PE format) calls an API in an external DLL goes through the import address table (IAT). When the app calls GetVersionEx, for example, execution jumps to the address recorded in the IAT.
When the app is loaded, the shim rewrites this IAT entry to the address of the shim code. This is the API hook. APIs obtained dynamically through GetProcAddress are handled by hooking GetProcAddress itself.1
The interposed shim returns an old OS version, reroutes file access, and so on, and calls the real API when needed. It shows the app “the Windows behavior it expects” and hands the OS a call that fits the current mechanism: an interpreter, so to speak.
flowchart TB
accTitle: How a shim intercepts an API call
accDescr: The app's API calls go through the IAT, and rewriting the IAT entry to point at the shim at load time lets the shim intercept, fake the same response an older Windows would give, and then call the real API when needed
app["App"] -->|API call| iat["IAT entry"]
iat -->|rewritten at load time to point at the shim| shim["Shim (interpreter)"]
shim -->|when needed| api["Real Windows API"]
shim -.-> lie["Fakes the same response an older Windows would give"]
gpa["Call through GetProcAddress"] -.->|handled by a hook| shim
Figure 5: The shim sits between the app and the Windows API. What is rewritten is the app’s IAT; the OS itself is unchanged.
What changes is the call path on the app side, not the OS itself. Because a shim runs under the same security constraints as the app, there is no need to relax the OS’s security settings in order to use one.1
4.2. The .sdb Decides Which EXE Gets What
The shim database is a binary file with the .sdb extension. It identifies executables by matching attributes such as file name, size, checksum, and version, and is consulted when a process starts. The terms used here break down as follows.7
| Term | Role |
|---|---|
| Appfix (shim) | Applies compatibility fixes such as API hooks |
| Apphelp | Displays a message such as “This app has a compatibility problem” |
| Compatibility layer (Compatibility mode) | Bundles several shims and flags and applies them together |
It is not only apps for which the user has set Compatibility mode that are matched. Every process start is checked against the OS’s standard database. Windows ships with fixes for thousands of known apps, stored under %WINDIR%\AppPatch. The compatibility fixes Microsoft provides ship as part of Windows and are updated through Windows Update.1
flowchart TB
accTitle: Shim database matching at process start
accDescr: Every process start is matched against the shim database, and if an entry matches the matching attributes, an Appfix injects shims or Apphelp displays a message, otherwise the process starts as is
start["Process start"] --> db["Matched against the shim database (.sdb)"]
db -.-> attr["Matched by file name, size, and so on"]
db --> hit{"Entry found?"}
hit -->|Yes| appfix["Appfix (inject shims)"]
hit -->|Yes| apphelp["Apphelp (display a message)"]
hit -->|No| plain["Start as is"]
layer["Compatibility layer (Compatibility mode)"] -.->|bundle of several shims and flags| appfix
Figure 6: Matching happens on every process start, not only for apps with Compatibility mode set.
A compatibility layer contains not only API hooks but also startup flags. RunAsInvoker in Section 7 is a compatibility fix that does not intercept an API; it acts on the execution level at startup as a loader flag.3
4.3. PCA May Also Detect a Problem and Apply a Fix
Even when no administrator sets anything by hand, the Program Compatibility Assistant (PCA) may apply compatibility settings. PCA monitors app execution and, when it detects signs of a known problem, either proposes a fix or, in some cases, applies it automatically.8
For example, a compatibility mode such as PINDLL is used for an app that crashes by calling code in an already-unloaded DLL, and WRPMITIGATION for an app that fails to write to protected Windows files.8
flowchart TB
accTitle: How PCA applies compatibility settings automatically
accDescr: PCA monitors app execution and, when it detects signs of a known compatibility problem, either proposes applying a fix to the user or, in some cases, applies the compatibility setting automatically
run["App execution"] --> pca["PCA monitors"]
pca --> sign{"Signs of a known problem?"}
sign -->|Yes| resp{"Which case?"}
resp -->|handled by a proposal| suggest["Proposes applying a fix"]
resp -->|some cases| auto["Applies the compatibility setting automatically"]
sign -->|No| none["Runs as is"]
auto -.-> ex["Examples such as PINDLL and WRPMITIGATION"]
Figure 7: PCA monitors app execution and, when it detects signs of a known problem, proposes a fix or applies it automatically.
“I never set it, but the Compatibility mode box was checked” is, in most cases, this path. It is not necessarily a fault or a mis-click; it can also be the result of Windows responding to a problem.
5. Choosing a Fix from the Symptom — Representative Shims and Version Checks
5.1. What Ready-Made Shims Can Supplement
Of the ready-made shims Microsoft publishes, these are the ones often used to extend the life of business apps.2
| Shim | What it can do (summary) |
|---|---|
| WinXPSP3VersionLie and other VersionLie-family shims | Return a specified older version to OS-version queries (version spoofing) |
| CorrectFilePaths | Reroute access to an unwritable or nonexistent file path to another location |
| VirtualRegistry | Redirect or spoof registry reads and writes (including version spoofing and simulating nonexistent keys) |
| ForceAdminAccess | Temporarily return True to an “is the user a member of the Administrators group?” check |
| RunAsAdmin / RunAsHighest / RunAsInvoker | Give, from the outside, an execution level equivalent to the manifest’s requireAdministrator / highestAvailable / asInvoker setting |
| WRPMitigation | Fake success for writes to protected OS files and registry keys so that the app can proceed |
| EmulateGetDiskFreeSpace | Report free disk space as at most 2 GB (for apps that overflow on large disks) |
| GlobalMemoryStatusLie | Spoof the reported memory-status values (for apps that fail a memory check at startup) |
| LoadLibraryRedirect | Load the current DLL on the Windows side instead of an old system DLL bundled with the app |
What most shims do is return the answer the old app expects. They reproduce, inside that process only, the assumptions of the era when the app was written: “disk capacity tops out at 2 GB,” “the OS is XP,” “the administrator check succeeds.” As Section 2 showed, faking an answer and actually changing privileges or resources are different things.
5.2. The Value of GetVersionEx Changes Even Without Selecting Compatibility Mode
Version spoofing is not only a matter of selecting Compatibility mode by hand. Since Windows 8.1, the value GetVersionEx returns depends on the app’s manifest. Without a <supportedOS> declaration inside <compatibility>, it returns 6.2, the Windows 8 value, even when the app is actually running on a newer Windows. With a declaration, it returns a value up to the highest OS declared. An app that declares GUIDs up through Windows 8.1, for example, sees 6.3 even on Windows 11.910
If a VersionLie-family shim is applied on top of that, it reports the version of the OS selected in Compatibility mode. You need to look at the default response determined by the manifest and then at the replacement made by the shim, in that order.9
flowchart TB
accTitle: How the OS version an app sees is determined
accDescr: The value GetVersionEx returns depends on whether the manifest has a supportedOS declaration, returning the Windows 8 value 6.2 without one and a value up to the highest declared OS with one, and if a VersionLie-family shim is applied it is overridden with the version of the selected OS
q["GetVersionEx query"] --> m{"supportedOS declared?"}
m -->|No| v62["Windows 8 value (6.2) returned"]
m -->|Yes| decl["Value up to the highest declared OS"]
v62 --> lie{"VersionLie shim applied?"}
decl --> lie
lie -->|Yes| fake["Version of the OS selected in Compatibility mode"]
lie -->|No| asis["Value returned as is"]
Figure 8: The Windows version an app sees is determined in stages by the manifest and the shim.
So where to look also depends on the symptom. If your own app is misidentifying Windows 11 as Windows 8 and that is causing trouble, check the supportedOS declaration first. If, on the other hand, an old app refuses to start solely because of a version check, VersionLie is worth trying. Its actual behavior is often fine on the newer OS, so this type has a high chance of having its startup refusal resolved by a shim.
flowchart TB
accTitle: Two version-related symptoms and their remedies
accDescr: If your own app identifies Windows 11 as Windows 8, suspect the manifest's supportedOS declaration, and an old app that refuses to start because of a version check can very likely be gotten past with a VersionLie shim
sym1["Windows 11 identified as Windows 8"] --> fix1["Suspect the supportedOS declaration"]
sym2["Refuses to start on a version check"] --> fix2["Try getting past it with VersionLie"]
fix2 -.-> why["Actual behavior is often fine on the newer OS"]
Figure 9: Suspect the manifest when the version comes out old, and VersionLie when startup is refused.
6. Check on One Machine First — The Compatibility Tab and the Layers Key
6.1. Look at Where the Checkboxes Are Saved
Settings saved on the Compatibility tab of the Properties dialog are written to the AppCompatFlags\Layers key in the registry. This is where compatibility layers are specified, and DXGI’s app-compatibility settings use it too.11
reg query "HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
For an EXE with “Windows XP (Service Pack 3),” “Run this program as an administrator,” and “Override high DPI scaling behavior” set, you see a value like the following.
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
C:\LegacyApp\Gyomu.exe REG_SZ ~ WINXPSP3 RUNASADMIN HIGHDPIAWARE
Representative correspondences between items and values are as follows. These are Windows 11 examples; item names and values can differ by OS version.
| Compatibility tab item | Value written (example) | What it actually is |
|---|---|---|
| Compatibility mode: Windows XP (Service Pack 3) | WINXPSP3 | A compatibility layer bundling version spoofing and several other shims |
| Reduced color mode: 8-bit (256) color | 256COLOR | Relaxation for the old color mode |
| Run in 640 x 480 screen resolution | 640X480 | Run at a low resolution |
| Disable fullscreen optimizations | DISABLEDXMAXIMIZEDWINDOWEDMODE | Disable rendering optimizations when in full screen |
| Override high DPI scaling behavior (Application) | HIGHDPIAWARE | Stop DPI virtualization (bitmap stretching)6 |
| Run this program as an administrator | RUNASADMIN | Request elevation at startup |
6.2. Separate Which User It Applies To from When It Applies
The HKCU side is a setting for that user only. If you save through “Change settings for all users,” it is written to the key of the same name on the HKLM side and applies to all users. When provisioning PCs, check which of the two you are saving to.
Also, the setting is applied to the process the next time that EXE starts. The loader reads the saved value and applies the corresponding compatibility layer.
flowchart TB
accTitle: How a Compatibility tab setting takes effect
accDescr: A Compatibility tab setting is saved to the Layers key under AppCompatFlags as the EXE path and a value, and 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["EXE path and value saved to the Layers key"]
reg --> boot["Next start of the EXE"]
boot --> loader["Loader reads the value"]
loader --> apply["Compatibility layer applied to the process"]
reg -.-> hkcu["HKCU is for that user only"]
reg -.-> hklm["HKLM applies to all users"]
Figure 10: The checkbox is really a write to the Layers key, and it is applied at the next startup.
6.3. Do Not Confuse Compatibility Mode with the Elevation Setting
RUNASADMIN lives in the same Layers key as WINXPSP3. When it looks as though “setting Compatibility mode added or removed elevation as well,” checking the value directly lets you separate the compatibility-layer setting from the elevation setting.
The Compatibility tab is the entry point to the representative ready-made layers. Selecting and combining individual shims is the job of Compatibility Administrator in Section 8. Before that, let us look at RunAsInvoker, which comes up often in day-to-day operations.
7. RunAsInvoker — Suppressing Only the Unnecessary Elevation Requests
7.1. Separate “Requests Administrator” from “Needs Administrator Privileges”
Some old business apps request UAC elevation every time they start, because of a requireAdministrator setting in the manifest or because the EXE name or contents cause them to be misdetected as an installer. Many of them, however, request it only because they inherited an XP-era design, and do not actually use administrator privileges.
RunAsInvoker overrides both installer detection and manifest processing and starts the app with the token inherited from the parent process. If the caller has standard-user privileges, it keeps those privileges.3
flowchart TB
accTitle: How RunAsInvoker suppresses elevation requests
accDescr: A requireAdministrator declaration in the manifest or misdetection as an installer causes a UAC elevation request at startup, but applying RunAsInvoker overrides both and starts the app with the token inherited from the parent process
manifest["requireAdministrator declaration"] --> shim{"RunAsInvoker applied?"}
detect["Misdetected as an installer"] --> shim
shim -->|No| uac["UAC elevation request at every start"]
shim -->|Yes| token["Starts with the parent's token"]
token -.-> limit["Work that needs administrator fails inside the app"]
Figure 11: RunAsInvoker only overrides the cause of the elevation request; it does not add privileges.
7.2. Try It Temporarily with an Environment Variable
Without creating an .sdb, you can apply the same layer temporarily with the __COMPAT_LAYER environment variable. The following examples apply it to child processes started from that Command Prompt or PowerShell session.
:: 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'
Adopt it only after confirming, with the same standard privileges as the actual users, that everything the business needs works.2 If the app does not need administrator privileges, putting the commands in a batch file and distributing it in place of a shortcut reduces both the handing out of local administrator rights and the calls to IT every time UAC asks for a password. It is a compatibility technique that moves in the direction of the principle of least privilege.
flowchart TB
accTitle: What distributing a RunAsInvoker batch file achieves
accDescr: Distributing a two-line batch file that sets RunAsInvoker in place of a shortcut means standard users do not need local administrator rights and IT is no longer called for UAC password prompts, an operation in line with the principle of least privilege
bat["Distribute a two-line batch file"] --> noadmin["No need to hand out administrator rights"]
bat --> nocall["IT not called for UAC prompts"]
noadmin --> lp["Operation in line with least privilege"]
nocall --> lp
Figure 12: Distributing a batch file alone reduces both the handing out of administrator rights and the calls for UAC help.
7.3. Distinguish the Scope of Effect, Permanent Application, and Fixing the App
RunAsInvoker does not add privileges. Writes to HKLM or updates under Program Files that truly need administrator privileges fail inside the app. When the conditions are met, they may instead be redirected to VirtualStore by UAC virtualization. If saving settings appears to have “stopped working,” suspect virtualization.5
The environment-variable approach affects only child processes that start with that environment inherited. For permanent application, set the Layers key directly or distribute an .sdb. There is no item on the Compatibility tab for selecting RUNASINVOKER.
flowchart TB
accTitle: Temporary and permanent application of RunAsInvoker
accDescr: Application through the COMPAT_LAYER environment variable affects only child processes started from there, and permanent application uses a direct setting in the Layers key or distribution through an .sdb
env["Set via environment variable"] --> child["Affects child processes only"]
child -.-> tmp["Temporary application"]
layers["Set directly in the Layers key"] --> always["Permanent application"]
sdb["Distribute via sdb"] --> always
Figure 13: The environment-variable approach is a temporary application limited to child processes; make it permanent with the Layers key or an .sdb.
If you can modify the app, the proper fix is to move the settings file location under %APPDATA% and declare asInvoker in the manifest, rather than piling on compatibility settings.3
8. Rolling Out to the Organization — Compatibility Administrator and sdbinst
8.1. Match the Tool’s 32-bit/64-bit Edition to the App
Compatibility Administrator is included in the Windows ADK (Windows Assessment and Deployment Kit).12 Both a 32-bit and a 64-bit edition are installed; use the 32-bit edition to fix 32-bit apps and the 64-bit edition for 64-bit apps.13
The other caution is not to conclude “it is fixed” from a test run as administrator. In an elevated state, UAC virtualization and redirection do not work as they normally would, and you can misjudge the result. Confirm the effect of a fix with the same account and privileges as the actual users.2
flowchart TB
accTitle: Two cautions when using Compatibility Administrator
accDescr: Use the 32-bit edition to fix 32-bit apps and the 64-bit edition for 64-bit apps, and confirm the effect of a fix not in an elevated state but with the same account and privileges as the actual users
app32["32-bit app"] --> tool32["Fix with the 32-bit edition"]
app64["64-bit app"] --> tool64["Fix with the 64-bit edition"]
elev["Test in an elevated state"] -.-> wrong["Risk of misjudging it as fixed"]
user["Test with the actual users' privileges"] --> ok["Effect confirmed correctly"]
Figure 14: Choosing between the 32-bit and 64-bit editions and testing with the actual users’ privileges are the cautions at the entrance.
8.2. Try a Layer First, Then Narrow Down to the Shims You Need and the Target EXE
Creating a custom compatibility database proceeds in the following order.14
- Create a new database under “Custom Databases” in the left pane, then choose “Create New” → “Application Fix.”
- Enter the app name and vendor name, and specify the target EXE file.
- Select a compatibility mode (layer) such as “Windows XP compatibility” and try the bundle of shims first.
- If necessary, select individual compatibility fixes. You can narrow it down to a minimal configuration such as VersionLie only or CorrectFilePaths only.
- Check the matching conditions such as file size, checksum, and version, then save.
Both “choose a shim that works” and “make it take effect only on the right EXE” are necessary. The default basic conditions are usually enough for matching, but keep a condition that identifies the app’s version. That way, once the vendor releases a fixed version, the old fix does not keep being applied to the new version.1415
flowchart TB
accTitle: Steps for creating a custom compatibility database
accDescr: Create an Application Fix in a new database, specify the app name and target EXE, try the Compatibility mode bundle, narrow down to individual shims if necessary, then check the 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 the Compatibility mode bundle"]
layer --> single["Narrow down to individual shims if needed"]
single --> match["Check the matching conditions and save"]
match -.-> ver["Keep a version-identifying condition"]
Figure 15: An Application Fix starts with the Compatibility mode bundle, is narrowed to a minimal configuration, and is limited to its target by matching conditions.
Try the .sdb you created on a test machine, confirm that it works as intended, and only then proceed to distribution.
8.3. Apply and Remove with sdbinst, and Manage Updates by GUID
To apply it on each PC, run sdbinst.exe with administrator privileges. Besides installing, you can uninstall by specifying the file or the database GUID.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}
In an organization where compatibility fixes keep growing, rather than giving each app installer its own .sdb, the recommended approach is to consolidate them into one custom database per company, or one per department, and manage them centrally. Updating and redistributing a consolidated database is easier to manage than tracking many one-line databases.15
A custom database has its own GUID. Installing a new version with the same GUID automatically replaces the old version. For distribution, use an existing channel that can run with administrator privileges, such as an MSI package or a startup script.15
flowchart TB
accTitle: From creating a custom .sdb to distributing it
accDescr: Create a custom compatibility database with Compatibility Administrator, test it on a test machine, apply it to each PC with sdbinst, and when updating, install a new version with the same GUID so that the old version is replaced automatically
make["Create with Compatibility Administrator"] --> test["Test on a test machine"]
test --> deploy["Apply to each PC with sdbinst"]
deploy --> update["Install a new version with the same GUID"]
update -.-> replace["Old version replaced automatically"]
deploy -.-> inv["Registered in Programs and Features"]
Figure 16: A custom .sdb is rolled out through creation, testing, and sdbinst distribution, and updates are managed by GUID.
An installed custom database is also registered in “Programs and Features” (Installed apps), which you can use for inventory and to confirm removal. Record which .sdb went onto which PC in the asset-management ledger as well.
9. Decide After It Runs — Maintaining the Life-Extension and Setting a Migration Deadline
9.1. Separate the Fixes the OS Provides from Your Own Organization’s Decisions to Apply Them
Even once a shim makes the app run, the app’s problem itself has not been fixed. A shim is a stopgap tailored to a specific way of using an API, and if the OS implementation changes, its premise can collapse.
The shims Microsoft provides are themselves maintained through Windows Update as part of Windows.1 On the other hand, managing what you applied through a custom database, and whether the business can continue on that combination, is your own organization’s job. Include verification at every feature update in the cost of life-extension.
flowchart TB
accTitle: Shims as a stopgap and the responsibility for maintaining them
accDescr: A shim is a lie tailored to a specific way of using an API and its premise collapses if the OS implementation changes, the shims Microsoft provides are maintained through Windows Update, but the lies applied through a custom database are looked after by your own organization, and verification at every feature update is the cost of life-extension
shim["A shim is a stopgap lie"] --> break["Premise collapses if the OS implementation changes"]
ms["Shims provided by Microsoft"] --> wu["Maintained through Windows Update"]
own["Lies in a custom database"] --> self["Looked after by your own organization"]
self --> cost["Verification at every feature update is the cost of life-extension"]
Figure 17: Responsibility for maintaining the lie that is a shim is split between what Microsoft provides and your own custom part.
9.2. Decide by Remaining Period of Use, Dependencies, and Verification Capacity
It is important not to decide on long-term use from the mere fact that “it ran under Compatibility mode.” Running on a shim means the app happened to fit into the safety net Windows provides. Decide by combining the following axes.
| Decision axis | Conditions leaning toward life-extension (shim) | Conditions leaning toward migration or rewrite |
|---|---|---|
| Remaining period of use | The business process itself is to be retired in 1 to 2 years | Expected to stay in use for 5 years or more |
| Source code | None (vendor gone or code lost) | Exists, or the asset can be recovered |
| Depth of dependencies | Only a user-mode API-compatibility problem | Depends on a driver, 16-bit code, or dedicated hardware |
| Alternatives | No packaged product or new version exists | The destination product or technology is clear |
| Impact of a failure | The business can continue on a fallback procedure even if it stops | Core business takes a direct hit |
| Verification capacity | Behavior can be checked at every feature update | No verification resources, so it tends to be left frozen |
9.3. If You Extend Its Life, Make Recording, Verification, and a Deadline a Set
- Record. Keep a record of which shim or layer was applied to which EXE, and why. Put the Layers key value and the
.sdbGUID in the ledger as well. The idea of not handing a “nobody knows why it works” state to the next person in charge is the same preservation covered in When You Inherit a System With No Source Code and No Documentation. - Verify. At each Windows feature update, confirm that the life-extended app starts and that its main operations work. Tie this to the OS replacement planning covered in Practical Options After Windows 10 End of Support.
- Set a deadline. Decide when the life-extension ends, such as “until the next core-system replacement” or “until March 2028,” and run the migration study in parallel.
flowchart TB
accTitle: The three-part operating set once you decide on life-extension
accDescr: Record in a ledger which shims the app runs on, verify the behavior of shim-extended apps at every feature update, set a deadline for the life-extension, and run the migration study in parallel
decide["Decide on life-extension"] --> rec["Record in a ledger which shims it runs on"]
rec --> verify["Verify behavior at every feature update"]
verify --> deadline["Set a deadline for ending the life-extension"]
deadline --> mig["Run the migration study in parallel"]
Figure 18: Life-extension is operated as a three-part set of recording, verification, and a deadline, with the migration study running in parallel.
9.4. Use Shims to Buy Time for Studying and Preparing the Migration
The migration options depend on the app’s technology. For a VB6 app, the starting point is the three choices laid out in How Long Will VB6 Apps Keep Running?: full rewrite, automated conversion, or phased migration. For an app that depends on ActiveX/OCX, the “keep, wrap, or replace” decision table in How to Handle ActiveX / OCX Today applies.
The healthy way to position shims is as a means of safely buying the time to study and prepare such a migration project.
flowchart TB
accTitle: Migration options and where shims fit
accDescr: The standard migration approach depends on the app's technology, with the three choices of full rewrite, automated conversion, and phased migration for VB6 and the keep, wrap, or replace decision table for ActiveX dependencies, while shims are positioned as a way to safely buy time for studying and preparing the migration project
tech{"What is the app's technology?"} -->|VB6| vb["Rewrite, automated conversion, or phased migration"]
tech -->|ActiveX dependency| ax["Keep, wrap, or replace"]
shim["Life-extension with shims"] -.->|buys time to study and prepare| tech
Figure 19: The standard migration approach is set by the app’s technology, and shims are positioned as a way to buy time for that study.
10. Summary
Compatibility mode is a mechanism that supplements, for the app alone, the behavior it expects from an older Windows. The shims at its center intercept API calls by replacing IAT entries and the like, and supplement version checks and access destinations. Windows itself uses them through its default database and PCA.
Think of the response in this order: first determine whether the problem is within a shim’s scope, confirm the settings you need with the Compatibility tab or RunAsInvoker, and for organization-wide rollout manage them with Compatibility Administrator and sdbinst. Choosing the 32-bit or 64-bit tool, verifying with the actual users’ privileges, and managing targets and versions through matching conditions and GUIDs are the practical essentials.
They are limited to user mode, however, and do not bypass security mechanisms. Kernel drivers, 16-bit apps on 64-bit Windows, and direct hardware access need other remedies. RunAsInvoker, too, only suppresses unnecessary elevation requests and does not add privileges.
Once it runs, record the settings, verify at every feature update, set a deadline, and run the migration in parallel. The decision to rely on Compatibility mode includes all of this.
The next time a single checkbox makes an old app run, ask yourself this: “Which lie is this app running on, and how long will that lie hold?” If you can answer, life-extension is a perfectly 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 behavior investigation and life-extension design for old business apps without source code (selecting shims and compatibility modes, creating and rolling out custom .sdb files), compatibility verification of existing apps for Windows 11 migration, and planning the rewrite or migration that runs alongside the life-extension. It is fine to consult us from the stage of “it ran under Compatibility mode, but is it all right to leave it this way?”
- Legacy Asset Reuse & Migration Support
- Windows App Development
- Technical Consulting & Design Review
- Contact
References
-
Microsoft Learn, Understanding and Using Compatibility Fixes. On compatibility fixes (shims) redirecting API calls by rewriting the IAT (import address table), dynamic linking being handled by hooking GetProcAddress, shims being subject to the same security constraints as the app and unable to bypass the OS’s security mechanisms, shims being limited to user mode and unable to fix driver problems, fixes possible with shims also being possible with code changes, usage scenarios such as apps whose vendor support has ended, and Microsoft-provided compatibility fixes shipping as part of Windows and being updated through Windows Update. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, Compatibility Fixes for Windows 10, Windows 8, Windows 7, and Windows Vista. A list and descriptions of known compatibility fixes such as CorrectFilePaths, VirtualRegistry, ForceAdminAccess, RunAsAdmin/RunAsHighest/RunAsInvoker, WRPMitigation, EmulateGetDiskFreeSpace, GlobalMemoryStatusLie, LoadLibraryRedirect, and the VersionLie family; the choice between the 32-bit and 64-bit editions of Compatibility Administrator; and the need to verify with the actual user account because virtualization and redirection do not work as expected when testing in an elevated state. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Using the RunAsInvoker Fix. On the RunAsInvoker compatibility fix starting the app with the token inherited from the parent process, overriding both installer detection and manifest processing, being applied as a loader flag without intercepting APIs, and declaring asInvoker in the manifest being the proper fix when the code can be changed. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Running 32-bit Applications. On WOW64 being the emulation layer that runs 32-bit apps on 64-bit Windows and isolates file and registry conflicts, and on 64-bit Windows not supporting the execution of 16-bit apps, whose startup fails with ERROR_BAD_EXE_FORMAT because of the number of significant bits in handles. ↩
-
Microsoft Learn, Registry Virtualization. On registry virtualization being a compatibility technology that transparently redirects global writes to HKLM\Software into a per-user VirtualStore, applying only to 32-bit interactive processes and being disabled for processes whose manifest specifies requestedExecutionLevel and for 64-bit processes, and being positioned as an interim technology to be removed from a future version of Windows. ↩ ↩2
-
Microsoft Learn, High DPI Desktop Application Development on Windows. On non-DPI-aware apps being treated as rendering at a fixed 96 DPI and looking blurry on high-DPI displays because Windows stretches the bitmap, and on the differences between the DPI awareness modes (Unaware/System/Per-Monitor). ↩ ↩2
-
Microsoft Learn, Application Compatibility Database. On the compatibility infrastructure managing problems and solutions in a database in .sdb format, matching by executable attributes, Apphelp (message display) and Appfix (API hooks by shims), and compatibility layers (modes) that bundle several shims and flags. ↩
-
Microsoft Learn, Program Compatibility Assistant scenarios for Windows 8. On PCA monitoring app execution to detect signs of known compatibility problems and proposing or automatically applying recommended fixes (PINDLL, DISABLEUSERCALLBACKEXCEPTION, VIRTUALIZEDELETE, WRPMITIGATION, and others), and on applying fixes from the Compatibility tab and the Program Compatibility Troubleshooter. ↩ ↩2
-
Microsoft Learn, GetVersionExW function. On the value returned by GetVersionEx depending on the manifest from Windows 8.1 onward, apps not manifested for Windows 8.1/10 receiving the Windows 8 version value (6.2), and the selected OS version being reported when a compatibility mode is in effect. ↩ ↩2
-
Microsoft Learn, Targeting your application for Windows. On how to declare the GUIDs of supported OS versions with the supportedOS element in the compatibility section of the app manifest, the behavior when there is no declaration, and 32-bit x86 apps that do not include trustInfo being subject to UAC file virtualization (redirection of writes to VirtualStore). ↩
-
Microsoft Learn, DXGI overview. On application compatibility settings being stored in the HKCU\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers registry key (using DXGI’s compatibility settings as the example). ↩
-
Microsoft Learn, Download and install the Windows ADK. On the Windows ADK including Compatibility Administrator and the Standard User Analyzer, how to think about choosing an ADK version, and how to download and install it. ↩
-
Microsoft Learn, Compatibility Administrator User’s Guide. On Compatibility Administrator providing the ability to apply compatibility fixes, compatibility modes, and AppHelp messages and to create custom databases, and on both a 32-bit and a 64-bit edition being installed, with the 32-bit edition required for 32-bit apps and the 64-bit edition for 64-bit apps. ↩
-
Microsoft Learn, Creating a Custom Compatibility Fix in Compatibility Administrator. On a compatibility fix (formerly called a shim) being a small piece of code that intercepts API calls, the procedure for creating an Application Fix in a custom database (specifying the app name, vendor, and target EXE, selecting a compatibility mode, selecting additional shims, and setting matching conditions), and keeping conditions that correctly identify the app while narrowing the matching information. ↩ ↩2
-
Microsoft Learn, Compatibility Fix Database Management Strategies and Deployment. On a centralized database being recommended as the management strategy for custom compatibility databases, including a version check (matching conditions) in a compatibility fix so that it is not applied to new versions, local installation with Sdbinst.exe (the -q, -u, and -g options), the automatic uninstallation of the old version when a new version with the same database GUID is installed, and distribution methods using MSI or scripts. ↩ ↩2 ↩3 ↩4
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
What Is an OLE Object? — How Embedding and Linking Work and the Pitfalls in Business Documents
An OLE object is what embeds an Excel table in Word. Learn embedding vs. linking, compound files, In-Place Activation, broken links, bloa...
Windows App Outsourcing and Custom Software Development: What to Sort Out Before You Ask
Before commissioning Windows app outsourcing or custom software development, here is how to sort out existing software modification, devi...
The Network Works but Windows Says "No internet" — Isolating NCSI, DNS, Proxy, and VPN on Windows
Why Windows says "No internet" while the network works, starting from the NCSI verdict. Isolate DNS, proxy, VPN, and captive portals with...
What Fast Startup Really Does — Why a Windows 'Shutdown' Is Not the Same as a Restart
A Windows shutdown is a hybrid shutdown by default, saving the kernel and drivers to hiberfil.sys. Why only a restart resets them, and wh...
Time Travel Debugging — Recording and Rewinding the Bugs That Never Reproduce in Long-Running Apps
A once-a-month bug leaves only its result in a crash dump. Record and rewind execution with WinDbg Time Travel Debugging (TTD): TTD.exe, ...
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 for the time being, yes. Compatibility mode is really a user-mode API hook called a shim, a mechanism that Windows provides officially as an OS setting. A shim is still only a stopgap for running an app without fixing it, though, and an OS update that changes its premise can break the app again. Record the fact that it runs under Compatibility mode in a ledger, and operate it together with a decision on whether to rewrite the app or to extend its life deliberately.
- What does the Compatibility mode checkbox actually do?
- When you save settings on the Compatibility tab of the Properties dialog, the path of the target EXE and a value such as "WINXPSP3" or "HIGHDPIAWARE" are written to 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. Under Windows XP compatibility mode, for example, version spoofing returns old values to the APIs that query the OS version. The behavior of the OS itself is not changed; only that process is shown a pretend older Windows.
- Can old 16-bit-era apps be run under Compatibility mode on 64-bit Windows?
- No. 64-bit Windows runs 32-bit apps through a mechanism called WOW64, but it does not support running 16-bit apps, and an attempt to start one fails with ERROR_BAD_EXE_FORMAT. This is an architectural limit that a shim cannot work around. Old packages in which only the launcher part of the installer is 16-bit fail for the same reason. If you truly need one, consider means other than Compatibility mode, such as a virtual machine containing 32-bit Windows.
- Can an app that "will not start unless run as administrator" be run with standard-user privileges?
- RunAsInvoker is worth trying. If you run set __COMPAT_LAYER=RunAsInvoker at a Command Prompt and then start the app, elevation requests caused by a requireAdministrator setting in the manifest or by installer detection are suppressed, and the app starts with the same (standard-user) privileges as the caller. For an app that only requests administrator privileges without actually using them, this alone removes elevation from day-to-day operation. Privileges do not increase, however, so work that truly needs administrator privileges fails inside that app. Adopt it after verifying the behavior.
- Where can I get Compatibility Administrator?
- It is included in the Windows ADK (Windows Assessment and Deployment Kit). Download the ADK from Microsoft's site and select the Application Compatibility Tools features during installation. Note that both a 32-bit and a 64-bit edition are installed, and you must use the 32-bit edition to fix 32-bit apps and the 64-bit edition for 64-bit apps. Apply a custom compatibility database (.sdb) you have created by running the sdbinst command on each PC.