How Windows App Compatibility Works — Keeping Old Apps Alive with Compatibility Mode, Shims, and Compatibility Administrator

· · 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.

Understanding the mechanism changes the quality of life-extensionUsing 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 rewriteUse without understanding the mechanismUnstable life-extension you dare not touchUse after understanding the mechanismDecisions with reasonsHow far you can rely on itWhat will break itWhen 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, GetVersionEx does 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=RunAsInvoker suppresses 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.

Where UAC virtualization sitsUAC 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 Windows32-bit interactive process with no manifestUAC virtualization appliesRedirected to a per-user VirtualStoreTemporary 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

How DPI virtualization worksAn 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 behaviorSwitches the virtualization behaviorApp that does not declare DPI awarenessTreated as drawing at 96 DPIBitmap is stretched for displayLooks blurry on a high-DPI monitorOverride high DPI scaling behavior

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.

The path by which a shim intercepts an API callAn 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 neededAPI callRewritten to the shim at load timeIf neededHandled by a hookAppIAT entryShim (interpreter)The real Windows APISpoofs the same response old Windows would have givenCalls via GetProcAddress

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

  1. 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.
  2. 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.
  3. 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

Matching the shim database at process startEvery 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-isNoYesProcess startMatch against .sdbFile name, size, etc.A registration?Start as-isWhich kind?Appfix: inject shimApphelp: a messageCompatibility layerMode = 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

How PCA applies a compatibility setting automaticallyPCA 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 automaticallyYesHandled by a proposalSome casesNoApp executionPCA watchesSigns of a known problem?Which case?Propose applying a fixApply a compatibility setting automaticallyRun as-isExample: 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.

  1. The value up to the OS declared in the manifest is returned (6.2 if there is no declaration)
  2. If Compatibility mode (a VersionLie-family shim) is applied, the version of the selected OS is returned6
How the OS version the app sees is decidedThe 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 versionNoYesYesNoGetVersionEx queryIs there a supportedOS declaration?Windows 8-equivalent (6.2) is returnedValue up to the highest declared OSVersionLie-family shim applied?The OS value chosen in Compatibility modeThe 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.

Two version-caused symptoms and how to deal with themIf 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 shimJudged as 8 even though this is Windows 11Suspect the supportedOS declarationStart refused on a version checkTry getting through with VersionLieThe 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.
The path from a Compatibility tab setting to taking effectCompatibility 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 processSet on the Compatibility tabStore the EXE path and value in the Layers keyNext EXE startThe loader reads the valueApply the compatibility layer to the processHKCU is that user onlyHKLM 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

Two caveats when using Compatibility AdministratorUse 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 state32-bit64-bitElevatedSame as userApp bitness?Use 32-bit editionUse 64-bit editionTest privileges?May mis-judge a fixConfirm the effect

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

  1. In Compatibility Administrator’s left pane, create a new database under “Custom Databases” and choose “Create New” → “Application Fix”
  2. Enter the app name and vendor name, and specify the target EXE file
  3. Choose the compatibility mode (layer) to apply — trying a bundle such as “Windows XP compatibility” first is the short path
  4. If needed, add individual compatibility fixes (shims) — you can narrow it to a minimal set such as VersionLie only or CorrectFilePaths only
  5. 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

Procedure for building a custom compatibility databaseCreate 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 saveCreate a new databaseChoose Application FixSpecify the app name and target EXETry a compatibility-mode bundleIf needed, narrow to individual shimsConfirm matching conditions and saveLeave 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

The path from creating a custom .sdb to distributing itCreate 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 automaticallyCreate in Compatibility AdministratorTest on a validation machineApply to each PC with sdbinstInstall a new version with the same GUIDThe old version is replaced automaticallyRegistered 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.
Cases where a shim does not workA 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 mechanismsDoes not workDoes not workDoes not workDoes not workShim (runs in user mode)Kernel driver16-bit appDirect hardware accessBypassing security mechanismsOn 64-bit, start itself failsOnly 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.

Shims as a stopgap, and who is responsible for maintaining themA 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-extensionShim = a stopgap lieOS change breaks itMicrosoft shimsVia Windows UpdateCustom-database liesOrg looks after themValidate each updateLife-extension cost

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

How RunAsInvoker suppresses an elevation requestA 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 parentNoYesrequireAdministrator declarationRunAsInvoker applied?Mis-detected as an installerUAC elevation request on every startStarts with the parent's tokenWork 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.

The effect of distributing a RunAsInvoker batchDistributing 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 privilegeDistribute a two-line batchYou can avoid handing out administrator privilegesIT is not called for UACAn operation that follows least privilege

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 declare asInvoker in the manifest — that is the correct shape.9
Temporary and permanent application of RunAsInvokerApplication 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 .sdbSet via an environment variableApplies only to child processesTemporary applicationSet directly in the Layers keyPermanent applicationDistribute via an sdb

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.

  1. 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”.
  2. 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).
  3. Set a deadline. Decide the end of life-extension — “until the next core-system refresh”, “until March 2028” — and run migration consideration in parallel.
The three-point operating set once you decide on life-extensionRecord 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 parallelDecide on life-extensionRecord: which shim makes it run, into a ledgerValidate: confirm behavior on every feature updateDeadline: decide the end of life-extensionRun 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.

Options on the migration side, and where a shim sitsThe 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 projectVB6ActiveX dependencyBuys time for consideration and preparationWhat is the app's technology?Rewrite, automatic conversion, or staged migrationKeep, wrap, or replaceLife-extension via a shim

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.

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.

References

  1. 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

  2. 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

  3. 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

  4. 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

  5. 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

  6. 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

  7. 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

  8. 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

  9. 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

  10. 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

  11. 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

  12. 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. 

  13. 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. 

  14. 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

  15. 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

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

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.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog