The well-known latin proverb
quod non est in actis, non est in mundo.
(english
What is not in the documents does not exist.
) does not apply
here.
User Account Protection was the preliminary name for a core security component of Windows Vista. The component has now been officially named User Account Control (UAC).How to disable User Account Control (UAC) on Windows Server
security feature(really: security theatre) User Account Control – programs which need or want to be run with administrative privileges and access rights have to ask the user for consent.
This made some (really: a minority of) users quite angry –
although these (rather braindead) users continued to
abuse the (privileged)
Protected Administrator
account created during
Windows Setup for their daily work (instead to
follow best practise
and use an unprivileged limited
alias standard
user account), they had to answer a prompt
whenever they wanted to perform an administrative task.
Unfortunately Microsoft heard these users and weakened
the security feature
(really:
security nightmare) – Windows 7
introduced auto-elevation
and enabled it for some 55 programs
shipped with Windows 7 and later versions, which
don’t prompt for consent any more.
Due to flaws in the design and deficiencies in the implementation of
User Account Control it can be bypassed trivially in
numerous ways with its auto-elevation
(mis)feature enabled.
As result, arbitrary programs can then be run with administrative
privileges and access rights without prompting the user for consent.
To defeat some of these trivial bypasses, auto-elevation
must
be disabled by moving the slider of the
User Account Control setting to its highest position
titled Always notify
, as documented and shown in the
MSKB
articles
975787
and
4462938.
Caveat: the slider position displayed in the
graphical user interface but does not always match
the effective setting – it shows Always notify
even if
the default setting
Notify me only when programs try to make changes to my computer
is configured!
Log on to the
user Protected Administrator
account created during Windows Setup.
Start one of the programs which have auto-elevation
enabled,
for example
NetPlWiz.exe
,
PrintUI.exe
or
WUSA.exe
– they start without to prompt for consent.
On a default installation of Windows 7 or later
versions of Windows NT perform the following 9 simple
steps.
Open Control Panel, then User Accounts and
click Change User Account Control setting
, then move the
slider to its highest position titled Always notify
and click
the button to apply the new setting.
Run the command line
"%SystemRoot%\System32\MMC.exe" "%SystemRoot%\System32\GPEdit.msc"
to start the
Local Group Policy Editor snap-in of
the Microsoft Management Console, or
execute the command line
"%SystemRoot%\System32\MMC.exe" "%SystemRoot%\System32\SecPol.msc"
to start the Local Security Policy
snap-in, answer the prompt for consent, then open the
Local Policies
folder and the Security Options
subfolder below it – the policy
User Account Control: Behavior of the elevation prompt for administrators in Admin Approval Mode
is displayed as Prompt for consent on the secure desktop
,
properly matching the setting applied in step 3.
Repeat step 2. – auto-elevating programs prompt for consent now.
Start the Registry Editor
RegEdit.exe
, answer the prompt
for consent, then open the registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
and delete the DWORD
registry entry
ConsentPromptBehaviorAdmin
present there.
Repeat step 4. – the policy
User Account Control: Behavior of the elevation prompt for administrators in Admin Approval Mode
is now properly displayed as Not Defined
.
Open Control Panel, then User Accounts
and click Change User Account Control setting
– the
slider is still displayed in its highest position
Always notify
.
Repeat step 2. – despite the unchanged
slider position Always notify
auto-elevating programs
don’t prompt for consent any more!
setting, but abuses a registry entry reserved for a
policyinstead, it misinterprets the default policy value
Not Definedand violates the more than 25 year old Designed for Windows guidelines!
[HKEY_CURRENT_USER\Software\‹company›\‹application›]
"‹setting›"=‹value›
or as
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\‹application›]
"‹setting›"=‹value›
[HKEY_CURRENT_USER\Software\Policies\‹company›\‹application›]
"‹policy›"=‹value›
or as
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\‹application›]
"‹policy›"=‹value›
[HKEY_LOCAL_MACHINE\SOFTWARE\‹company›\‹application›]
"‹setting›"=‹value›
or as
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\‹application›]
"‹setting›"=‹value›
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\‹company›\‹application›]
"‹policy›"=‹value›
or as
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\‹application›]
"‹policy›"=‹value›
Every process has an environment block that contains a set of environment variables and their values. There are two types of environment variables: user environment variables (set for each user) and system environment variables (set for everyone).By default, a child process inherits the environment variables of its parent process. […]
[…] To programmatically add or modify system environment variables, add them to the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment registry key, […]
Environment variables specify search paths for files, directories for temporary files, application-specific options, and other similar information. The system maintains an environment block for each user and one for the computer. The system environment block represents environment variables for all users of the particular computer. A user's environment block represents the environment variables the system maintains for that particular user, including the set of system environment variables.Both articles but fail to tell that two kinds of user environment variables exist, persistent and volatile, that volatile environment variables obscure persistent environment variables with the same name, how to add, modify or remove them, and where they are stored – persistent user environment variables are stored in the registry keyBy default, each process receives a copy of the environment block for its parent process. Typically, this is the environment block for the user who is logged on. […]
HKEY_CURRENT_USER\Environment
alias
HKEY_USERS\‹security identifier›\Environment
,
while volatile user environment variables are stored in the
(volatile) registry key
HKEY_CURRENT_USER\Volatile Environment
alias
HKEY_USERS\‹security identifier›\Volatile Environment
,
where they are created during user logon and discarded when the user
logs off.
HKEY_CURRENT_USER
The articles also fail to tell that user environment variables obscure system environment variables of the same name – with but four notable exceptions:
LibPath
,
OS2LibPath
and Path
are assigned to the
respective process environment variable during user logon;
NT AUTHORITY\SYSTEM
alias
LocalSystem
get the system environment variables TEMP
and
TMP
instead of the respective user environment
variables!
Thanksto the braindead (mis)behaviour listed last, privileged processes running under the user account
NT AUTHORITY\SYSTEM
alias
LocalSystem
use the public user-writable and therefore unsafe
directory %SystemRoot%\Temp\
instead of their private
and safe directory
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
,
allowing unprivileged users to tamper with (executable) files
created there by these privileged processes, eventually resulting in
local escalation of privilege.
Note: see the Security Advisory ADV170017 for just one example of such a vulnerability.
Both articles also fail to tell that not all system environment
variables are stored in the registry key
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
– the system environment variables
ALLUSERSPROFILE
, COMPUTERNAME
,
PROCESSOR_ARCHITEW6432
, PUBLIC
,
CommonProgramFiles
,
CommonProgramFiles(x86)
,
CommonProgramW6432
,
ProgramData
, ProgramFiles
,
ProgramFiles(x86)
, ProgramW6432
,
SystemDrive
and SystemRoot
are created
programmatically, similar to the volatile user environment variables
created during user logon.
Note: they are but obscured by persistent system environment variables as well as persistent and volatile user environment variables with the same name stored in the registry keys shown above!
And last, both articles fail to mention the immutable dynamic system
environment variables FIRMWARE_TYPE
,
NUMBER_OF_PROCESSORS
, __APPDIR__
and
__CD__
which are nowhere stored and not present in the
process environment block, but evaluated upon expansion.
Note: they are not obscured by regular environment variables of the same name!
The MSDN article WOW64 Implementation Details states:
When a 32-bit process is created by a 64-bit process, or when a 64-bit process is created by a 32-bit process, WOW64 sets the environment variables for the created process as shown in the following table.
Process Environment variables 64-bit process PROCESSOR_ARCHITECTURE=AMD64 or PROCESSOR_ARCHITECTURE=IA64 or PROCESSOR_ARCHITECTURE=ARM64
ProgramFiles=%ProgramFiles%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles%
CommonProgramW6432=%CommonProgramFiles%
Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: The ProgramW6432 and CommonProgramW6432 environment variables were added starting with Windows 7 and Windows Server 2008 R2.32-bit process PROCESSOR_ARCHITECTURE=x86
PROCESSOR_ARCHITEW6432=%PROCESSOR_ARCHITECTURE%
ProgramFiles=%ProgramFiles(x86)%
ProgramW6432=%ProgramFiles%
CommonProgramFiles=%CommonProgramFiles(x86)%
CommonProgramW6432=%CommonProgramFiles%
%SystemRoot%\Profiles\%USERNAME%\
into new directories
%SystemDrive%\Documents and Settings\%USERNAME%\
.
NT AUTHORITY\SYSTEM
alias
LocalSystem
in the new directory
%SystemRoot%\System32\Config\SystemProfile\
.
Note: its location was a rather
braindead choice – it is subject to
file system redirection
on 64-bit editions of Windows NT, where two separate
directories
%SystemRoot%\System32\Config\SystemProfile\
and
%SystemRoot%\SysWoW64\Config\SystemProfile\
exist!
The world-writable Temp
directory
%SystemRoot%\Temp\
, shared by all users in previous
versions of Windows NT, was replaced with separate
private Temp
directories
%USERPROFILE%\Local Settings\Temp\
alias
%SystemDrive%\Documents and Settings\%USERNAME%\Local Settings\Temp\
located within the user profiles – except for
the
LocalSystem
user account, which continued (and still continues) to use the
(still world-writable) directory %SystemRoot%\Temp\
!
Windows XP added the (less privileged) user accounts
NT AUTHORITY\LOCAL SERVICE
alias
LocalService
and NT AUTHORITY\NETWORK SERVICE
alias
NetworkService
,
placed their user profiles in the directories
%SystemDrive%\Documents and Settings\LocalService\
and
%SystemDrive%\Documents and Settings\NetworkService\
,
set their user environment variables TEMP
and
TMP
to %USERPROFILE%\Local Settings\Temp
,
and created a private Temp
directory within both user
profiles.
Windows Vista relocated these two service profiles to
the new directories
%SystemRoot%\ServiceProfiles\LocalService\
and
%SystemRoot%\ServiceProfiles\NetworkService\
, relocated
all regular user profiles
%SystemDrive%\Documents and Settings\%USERNAME%\
to
the directories %SystemDrive%\Users\%USERNAME%\
, but
kept the profiles
%SystemRoot%\System32\Config\SystemProfile\
and
%SystemRoot%\SysWoW64\Config\SystemProfile\
.
All user accounts except
LocalSystem
kept their private Temp
directory, now
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemDrive%\Users\%USERNAME%\AppData\Local\Temp\
for
the regular user accounts and
%SystemRoot%\ServiceProfiles\LocalService\AppData\Local\Temp\
respectively
%SystemRoot%\ServiceProfiles\NetworkService\AppData\Local\Temp\
for the service user accounts.
At least since Windows 7 the user environment variables
TEMP
and TMP
are set in the
LocalSystem
user account too, despite the directory
%USERPROFILE%\AppData\Local\Temp\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
or
%SystemRoot%\SysWoW64\Config\SystemProfile\AppData\Local\Temp\
is missing in its user profiles!
The MSDN article Profiles Directory provides additional information.
Create the text file quirk1.vbs
with the following
content in an arbitrary, preferable empty directory:
Rem Copyright © 1999-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("WScript.Shell")
WScript.StdOut.WriteLine "Environment Variables"
For Each strScope In Array("PROCESS", "SYSTEM", "USER", "VOLATILE")
WScript.StdOut.WriteLine
WScript.StdOut.WriteLine "Scope '" & strScope & "': " & .Environment(strScope).Count & " items"
For Each strItem In .Environment(strScope)
WScript.StdOut.WriteLine vbTab & strItem
Next
Next
End With
Execute the
VBScript
quirk1.vbs
created in step 1. under the
LocalSystem
user account to list the environment variables of all scopes:
CSCRIPT.EXE quirk1.vbs
Microsoft (R) Windows Script Host, Version 5.812 Copyright (C) Microsoft Corporation. All rights reserved. Environment Variables Scope 'PROCESS': 31 items ALLUSERSPROFILE=C:\ProgramData APPDATA=C:\Windows\system32\config\systemprofile\AppData\Roaming CommonProgramFiles=C:\Program Files\Common Files CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files CommonProgramW6432=C:\Program Files\Common Files COMPUTERNAME=AMNESIAC ComSpec=C:\Windows\system32\cmd.exe DriverData=C:\Windows\System32\Drivers\DriverData LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData=C:\ProgramData ProgramFiles=C:\Program Files ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PSModulePath=%ProgramFiles%\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules PUBLIC=C:\Users\Public SystemDrive=C: SystemRoot=C:\Windows TEMP=C:\Windows\TEMP TMP=C:\Windows\TEMP USERDOMAIN=KANTHAK USERNAME=AMNESIAC$ USERPROFILE=C:\Windows\system32\config\systemprofile windir=C:\Windows Scope 'SYSTEM': 15 items ComSpec=%SystemRoot%\system32\cmd.exe DriverData=C:\Windows\System32\Drivers\DriverData OS=Windows_NT Path=%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\ PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PSModulePath=%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules\ TEMP=%SystemRoot%\TEMP TMP=%SystemRoot%\TEMP USERNAME=SYSTEM windir=%SystemRoot% NUMBER_OF_PROCESSORS=4 PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 Scope 'USER': 3 items PATH=%USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP=%USERPROFILE%\AppData\Local\Temp TMP=%USERPROFILE%\AppData\Local\Temp Scope 'VOLATILE': 0 itemsOops: although the user environment variables
TEMP
and TMP
exist, the process
environment variables TEMP
and TMP
were
set from the system environment variables!
Start the Command Processor
Cmd.exe
under the
LocalSystem
user account, then list all environment variables and (the contents
of) the directory %LOCALAPPDATA%\
alias
%USERPROFILE%\AppData\Local\
alias
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\
to determine whether a subdirectory Temp\
exists there:
SET DIR /A "%LOCALAPPDATA%"
ALLUSERSPROFILE=C:\ProgramData APPDATA=C:\Windows\system32\config\systemprofile\AppData\Roaming CommonProgramFiles=C:\Program Files\Common Files CommonProgramFiles(x86)=C:\Program Files (x86)\Common Files CommonProgramW6432=C:\Program Files\Common Files COMPUTERNAME=AMNESIAC ComSpec=C:\Windows\system32\cmd.exe DriverData=C:\Windows\System32\Drivers\DriverData LOCALAPPDATA=C:\Windows\system32\config\systemprofile\AppData\Local NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Windows\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 96 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData=C:\ProgramData ProgramFiles=C:\Program Files ProgramFiles(x86)=C:\Program Files (x86) ProgramW6432=C:\Program Files PROMPT=$P$G PSModulePath=C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules PUBLIC=C:\Users\Public SystemDrive=C: SystemRoot=C:\Windows TEMP=C:\Windows\TEMP TMP=C:\Windows\TEMP USERDOMAIN=KANTHAK USERNAME=AMNESIAC$ USERPROFILE=C:\Windows\system32\config\systemprofile windir=C:\Windows Volume in drive C has no label. Volume Serial Number is 1957-0427 Directory of C:\Windows\system32\config\systemprofile\AppData\Local 04/27/2019 08:15 PM <DIR> . 04/27/2019 08:15 PM <DIR> .. 04/27/2019 08:15 PM <DIR> Microsoft 0 File(s) 0 bytes 3 Dir(s) 9,876,543,210 bytes freeOops: a subdirectory
Temp\
does not
exist!
Query the registry keys
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
,
HKEY_USERS\S-1-5-18\Environment
and
HKEY_USERS\S-1-5-18\Volatile Environment
to list the
environment variables stored in the registry:
REG.EXE QUERY "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" REG.EXE QUERY "HKEY_USERS\S-1-5-18\Environment" REG.EXE QUERY "HKEY_USERS\S-1-5-18\Volatile Environment"Note: the MSKB article 243300 gives the well-known SID
S-1-5-18
for the
NT AUTHORITY\SYSTEM
alias
LocalSystem
user account.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment ComSpec REG_EXPAND_SZ %SystemRoot%\system32\cmd.exe DriverData REG_SZ C:\Windows\System32\Drivers\DriverData OS REG_SZ Windows_NT Path REG_EXPAND_SZ %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\ PATHEXT REG_SZ .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE REG_SZ AMD64 PSModulePath REG_EXPAND_SZ %ProgramFiles%\WindowsPowerShell\Modules;%SystemRoot%\system32\WindowsPowerShell\v1.0\Modules TEMP REG_EXPAND_SZ %SystemRoot%\TEMP TMP REG_EXPAND_SZ %SystemRoot%\TEMP USERNAME REG_SZ SYSTEM windir REG_EXPAND_SZ %SystemRoot% NUMBER_OF_PROCESSORS REG_SZ 4 PROCESSOR_IDENTIFIER REG_SZ Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL REG_SZ 6 PROCESSOR_REVISION REG_SZ 5e03 HKEY_USERS\S-1-5-18\Environment Path REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp TMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp ERROR: The specified registry key or value was not found.
Query the registry keys HKEY_CURRENT_USER\Environment
and HKEY_CURRENT_USER\Volatile Environment
of a
standard user account for comparison:
REG.EXE QUERY "HKEY_CURRENT_USER\Environment" REG.EXE QUERY "HKEY_CURRENT_USER\Volatile Environment" /S
HKEY_CURRENT_USER\Environment Path REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Microsoft\WindowsApps; TEMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp TMP REG_EXPAND_SZ %USERPROFILE%\AppData\Local\Temp HKEY_CURRENT_USER\Volatile Environment LOGONSERVER REG_SZ \\AMNESIAC USERDOMAIN REG_SZ AMNESIAC USERNAME REG_SZ Stefan USERPROFILE REG_SZ C:\Users\Stefan HOMEPATH REG_SZ \Users\Stefan HOMEDRIVE REG_SZ C: APPDATA REG_SZ C:\Users\Stefan\AppData\Roaming LOCALAPPDATA REG_SZ C:\Users\Stefan\AppData\Local USERDOMAIN_ROAMINGPROFILE REG_SZ AMNESIAC HKEY_CURRENT_USER\Volatile Environment\1 SESSIONNAME REG_SZ Console CLIENTNAME REG_SZ
%SystemRoot%\System32\Config\SystemProfile\AppData\Local\Temp\
(on 64-bit systems
%SystemRoot%\SysWoW64\Config\SystemProfile\AppData\Local\Temp\
too), then start the program
SystemPropertiesAdvanced.exe
,
click the button ,
replace the value %SystemRoot%\TEMP
of the system
environment variables TEMP
and TMP
with
%USERPROFILE%\AppData\Local\Temp
, save the changed
settings and reboot the system.
Caveat: on 64-bit systems, the disjoint directories might cause surprising (mis)behaviour!
Create the text file quirk1.cmd
with the following
content in an arbitrary, preferable empty directory:
REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
@IF NOT DEFINED SystemRoot EXIT /B
@CHDIR /D "%SystemRoot%"
@IF NOT EXIST "SysWoW64\cmd.exe" EXIT /B
@FOR /F "Delims==" %%? IN ('SET') DO @SET "%%?="
@SET SystemRoot=%CD%
@IF EXIST "SysNative\cmd.exe" (
"SysNative\cmd.exe" /D /C "SET & EXIT"
) ELSE (
"SysWoW64\cmd.exe" /D /C "SET & CALL ""%~f0"" & PAUSE & EXIT"
)
@EXIT /B
On a 64-bit system, start the batch script quirk1.cmd
created in step 1. per double-click:
REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de> COMSPEC=C:\Windows\SysWoW64\cmd.exe PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC PROMPT=$P$G SystemRoot=C:\Windows REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de> COMSPEC=C:\Windows\system32\cmd.exe PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC PROMPT=$P$G SystemRoot=C:\Windows Press any key to continue . . .OOPS: if one of the environment variables
CommonProgramFiles
,
CommonProgramFiles(x86)
, ProgramFiles
,
ProgramFiles(x86)
or
PROCESSOR_ARCHITECTURE
is not present in the
environment block of the parent process, the derived environment
variables CommonProgramFiles
and
CommonProgramW6432
respectively
ProgramFiles
and ProgramW6432
are not set
in 64-bit processes started from a 32-bit process, while in 32-bit
processes started from a 64-bit process the derived environment
variables CommonProgramFiles
,
CommonProgramW6432
, ProgramFiles
,
ProgramW6432
respectively
PROCESSOR_ARCHITEW6432
as well as the inherited
environment variable PROCESSOR_ARCHITECTURE
are
missing!
Start the Command Processor
Cmd.exe
, then execute the
following command lines:
SET NUMBER_OF_PROCESSORS= IF DEFINED NUMBER_OF_PROCESSORS SET NUMBER_OF_PROCESSORS ECHO %NUMBER_OF_PROCESSORS% SET NUMBER_OF_PROCESSORS=quirk SET NUMBER_OF_PROCESSORS ECHO %NUMBER_OF_PROCESSORS% IF DEFINED __APPDIR__ SET __APPDIR__ ECHO %__APPDIR__% SET __APPDIR__=quirk SET __APPDIR__ ECHO %__APPDIR__% IF DEFINED __CD__ SET __CD__ ECHO %__CD__% SET __CD__=quirk SET __CD__ ECHO %__CD__%Note: the command lines can be copied and pasted as block into a Command Processor window.
Environment variable NUMBER_OF_PROCESSORS not defined! 4 NUMBER_OF_PROCESSORS=quirk 4 Environment variable __APPDIR__ not defined! C:\Windows\system32\ __APPDIR__=quirk C:\Windows\system32\ Environment variable __CD__ not defined C:\Users\Stefan\Desktop\ __CD__=quirk C:\Users\Stefan\Desktop\OUCH¹: the undocumented (dynamic) environment variables
NUMBER_OF_PROCESSORS
,
__APPDIR__
and __CD__
are both defined and
not defined – they exhibit a quantum superposition like a
qubit!
OUCH²: a (regular) environment variable
NUMBER_OF_PROCESSORS
, __APPDIR__
or
__CD__
shows two different values – again a
quantum superposition!
NOTE: the undocumented (dynamic) environment
variables FIRMWARE_TYPE
(introduced with
Windows 8), NUMBER_OF_PROCESSORS
,
__APPDIR__
and __CD__
are created
(read-only) deep down in the bowels of
NTDLL.dll
– they are
evaluated by the Win32 functions
ExpandEnvironmentStrings()
,
ExpandEnvironmentStringsForUser()
and
GetEnvironmentVariable()
even if a regular environment variable of the same name exists in
the process’ environment block!
Create the text file quirk1.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <userenv.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const LPCWSTR szQuirk[] = {L"ALLUSERSPROFILE",
L"APPDATA",
L"CommonAppData",
L"CommonProgramFiles",
L"CommonProgramFiles(x86)",
L"CommonProgramW6432",
L"COMSPEC",
L"DriverData",
L"HOMEDRIVE",
L"HOMEPATH",
L"HOMESHARE",
L"LOCALAPPDATA",
L"OneDrive",
L"PATH",
L"ProgramData",
L"ProgramFiles",
L"ProgramFiles(x86)",
L"ProgramW6432",
L"PSModulePath",
L"PUBLIC",
L"SystemDrive",
L"SystemRoot",
L"TEMP",
L"TMP",
L"USERPROFILE",
L"windir"};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError;
DWORD dwQuirk = 0;
LPWSTR lpQuirk;
LPWSTR lpBlock;
HKEY hk;
HANDLE hToken;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
#ifndef QUIRKS
do
if (!SetEnvironmentVariable(szQuirk[dwQuirk], L""))
PrintConsole(hConsole,
L"SetEnvironmentVariable() returned error %lu\n",
dwError = GetLastError());
while (++dwQuirk < sizeof(szQuirk) / sizeof(*szQuirk));
lpBlock = GetEnvironmentStrings();
if (lpBlock == NULL)
PrintConsole(hConsole,
L"GetEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
else
{
for (lpQuirk = lpBlock;
lpQuirk[0] != L'\0';
lpQuirk[dwQuirk = wcslen(lpQuirk)] = L'\n', lpQuirk += ++dwQuirk)
continue;
if (!WriteConsole(hConsole, lpBlock, dwQuirk = lpQuirk - lpBlock, &dwError, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (dwError ^= dwQuirk)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!FreeEnvironmentStrings(lpBlock))
PrintConsole(hConsole,
L"FreeEnvironmentStrings() returned error %lu\n",
dwError = GetLastError());
}
#else // QUIRKS
#if QUIRKS == 3
dwError = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",
#elif QUIRKS == 2
dwError = RegOpenKeyEx(HKEY_CURRENT_USER,
L"Environment",
#elif QUIRKS <= 1
dwError = RegOpenKeyEx(HKEY_CURRENT_USER,
L"Volatile Environment",
#else
#error QUIRKS out of range from 0 to 3!
#endif
REG_OPTION_RESERVED,
KEY_SET_VALUE,
&hk);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"RegOpenKeyEx() returned error %lu\n",
dwError);
else
{
do
{
dwError = RegSetValueEx(hk,
szQuirk[dwQuirk],
0,
REG_SZ,
#if QUIRKS == 0
NULL,
0);
#else
L"",
sizeof(L""));
#endif
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"RegSetValueEx() returned error %lu\n",
dwError);
}
while (++dwQuirk < sizeof(szQuirk) / sizeof(*szQuirk));
dwError = RegCloseKey(hk);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"RegCloseKey() returned error %lu\n",
dwError);
}
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
PrintConsole(hConsole,
L"OpenProcessToken() returned error %lu\n",
dwError = GetLastError());
else
{
if (!CreateEnvironmentBlock(&lpBlock, hToken, FALSE))
PrintConsole(hConsole,
L"CreateEnvironmentBlock() returned error %lu\n",
dwError = GetLastError());
else
{
for (lpQuirk = lpBlock;
lpQuirk[0] != L'\0';
lpQuirk[dwQuirk = wcslen(lpQuirk)] = L'\n', lpQuirk += ++dwQuirk)
continue;
if (!WriteConsole(hConsole, lpBlock, dwQuirk = lpQuirk - lpBlock, &dwError, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else
if (dwError ^= dwQuirk)
dwError = ERROR_WRITE_FAULT;
// else
// dwError = ERROR_SUCCESS;
if (!DestroyEnvironmentBlock(lpBlock))
PrintConsole(hConsole,
L"DestroyEnvironmentBlock() returned error %lu\n",
GetLastError());
}
if (!CloseHandle(hToken))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
#endif // QUIRKS
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk1.c advapi32.lib kernel32.lib user32.lib userenv.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk1.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c quirk1.c(65) : warning C4101: 'hToken' : unreferenced local variable quirk1.c(64) : warning C4101: 'hk' : unreferenced local variable Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Execute the console application quirk1.exe
built in
step 2. to show proper behaviour first:
.\quirk1.exe
=::=::\ =C:=C:\Users\Stefan\Desktop =ExitCode=00000000 ALLUSERSPROFILE= APPDATA= CL=/GAFy /Oisy /W4 /Zl CommonAppData= CommonProgramFiles= CommonProgramFiles(x86)= CommonProgramW6432= COMPUTERNAME=AMNESIAC ComSpec= DriverData= HOMEDRIVE= HOMEPATH= HOMESHARE= LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE LOCALAPPDATA= LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OneDrive= OS=Windows_NT Path= PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=x86 PROCESSOR_ARCHITEW6432=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData= ProgramFiles= ProgramFiles(x86)= ProgramW6432= PROMPT=$P$G PSModulePath= PUBLIC= SESSIONNAME=Console SystemDrive= SystemRoot= TEMP= TMP= USERDOMAIN=AMNESIAC USERDOMAIN_ROAMINGPROFILE=AMNESIAC USERNAME=Stefan USERPROFILE= windir=Note: (all 26) empty environment variables are present in the process environment block.
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1. a
second time, now with the preprocessor macro QUIRKS
defined as 0:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=0 quirk1.c advapi32.lib kernel32.lib user32.lib userenv.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Export the registry entries from the registry key
HKEY_CURRENT_USER\Volatile Environment
to the file
quirk1.reg
:
REG.EXE EXPORT "HKEY_CURRENT_USER\Volatile Environment" quirk1.reg
The operation completed successfully.
Execute the console application quirk1.exe
built in
step 4. to show the first bug:
.\quirk1.exe
ALLUSERSPROFILE=AMNESIAC APPDATA=Stefan CommonAppData=AMNESIAC CommonProgramFiles=AMNESIAC CommonProgramFiles(x86)=AMNESIAC CommonProgramW6432=AMNESIAC COMPUTERNAME=AMNESIAC ComSpec=AMNESIAC DriverData=AMNESIAC HOMEDRIVE=Stefan HOMEPATH=Stefan HOMESHARE=Stefan LOCALAPPDATA=Stefan LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OneDrive=AMNESIAC OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;AMNESIAC PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 ProgramData=AMNESIAC ProgramFiles=AMNESIAC ProgramFiles(x86)=AMNESIAC ProgramW6432=AMNESIAC PSModulePath=AMNESIAC PUBLIC=AMNESIAC SESSIONNAME=Console SystemDrive=AMNESIAC SystemRoot=AMNESIAC TEMP=AMNESIAC TMP=AMNESIAC USERDOMAIN_ROAMINGPROFILE=AMNESIAC USERPROFILE=Stefan windir=AMNESIACOUCH¹: volatile user environment variables stored without value in the Registry are written to the environment block with the value of arbitrary other now missing environment variables, here
COMPUTERNAME
and USERPROFILE
, and they
obscure programmatic system environment variables!
(Optional) If you are curious, execute the following command line to
signal the (graphical) Shell
Explorer.exe
that environment
variables have changed and watch what happens when you start some
applications:
SETX.EXE QUIRKS ""
SUCCESS: Specified value was saved.Note: when you've seen enough havoc and malfunctions, just log off and log on again, then continue with step 9. – volatile registry keys and their entries don’t survive logoff and shutdown!
Restore the (volatile) registry entries from the file
quirk1.reg
created in step 5.:
REG.EXE DELETE "HKEY_CURRENT_USER\Volatile Environment" /VA /F REG.EXE IMPORT quirk1.reg
The operation completed successfully. The operation completed successfully.
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1. a
third time, now with the preprocessor macro QUIRKS
defined as 1:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=1 quirk1.c advapi32.lib kernel32.lib user32.lib userenv.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Execute the console application quirk1.exe
built in
step 9. to show the second bug:
.\quirk1.exe
COMPUTERNAME=AMNESIAC LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\; PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 SESSIONNAME=Console USERDOMAIN_ROAMINGPROFILE=AMNESIACOUCH²: volatile user environment variables stored with empty value in the Registry are not written to the environment block, and they inhibit the addition of programmatic system environment variables with the same name to the environment block!
Oops¹: the previously present environment
variables USERDOMAIN
and USERNAME
are
missing too!
(Optional) If you are curious, execute the following command line to
signal the (graphical) Shell
Explorer.exe
that environment
variables have changed and watch what happens when you start some
applications:
SETX.EXE QUIRKS ""
SUCCESS: Specified value was saved.Note: when you've seen enough havoc and malfunctions, just log off and log on again, then continue with step 13. – volatile registry keys and their entries don’t survive logoff and shutdown!
Restore the (volatile) registry entries from the file
quirk1.reg
created in step 5.:
REG.EXE DELETE "HKEY_CURRENT_USER\Volatile Environment" /VA /F REG.EXE IMPORT quirk1.reg
The operation completed successfully. The operation completed successfully.
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1. a
fourth time, now with the preprocessor macro QUIRKS
defined as 2:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=2 quirk1.c advapi32.lib kernel32.lib user32.lib userenv.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Export the registry entries from the registry key
HKEY_CURRENT_USER\Environment
to the file
quirk1.reg
:
REG.EXE EXPORT "HKEY_CURRENT_USER\Environment" quirk1.reg /F
The operation completed successfully.
Execute the console application quirk1.exe
built in
step 13. to show the second bug again:
.\quirk1.exe
COMPUTERNAME=AMNESIAC LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\; PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 SESSIONNAME=Console USERDOMAIN_ROAMINGPROFILE=AMNESIACOUCH³: persistent user environment variables stored with empty value in the Registry are not written to the environment block, and they inhibit the addition of programmatic system environment variables with the same name to the environment block!
Oops²: the previously present environment
variables USERDOMAIN
and USERNAME
are
missing too, again!
(Optional) If you are curious, execute the following command line to
signal the (graphical) Shell
Explorer.exe
that environment
variables have changed and watch what happens when you start some
applications, then log off and log on again:
SETX.EXE windir ""
SUCCESS: Specified value was saved.Note: since the Shell won’t start any more, your administrator needs to load your registry hive, then restore the values of the registry entries
PATH
,
TEMP
and TMP
to their default values,
remove the 22 remaining empty registry entries and finally unload
the registry hive before you can continue with step 18.:
REG.EXE LOAD HKU\Quirks "%SystemDrive%\Users\‹account›\NTUSER.DAT" REG.EXE ADD HKU\Quirks\Environment /V PATH /T REG_EXPAND_SZ /D %USERPROFILE^%\AppData\Local\Microsoft\WindowsApps /F REG.EXE ADD HKU\Quirks\Environment /V TEMP /T REG_EXPAND_SZ /D %USERPROFILE^%\AppData\Local\Temp /F REG.EXE ADD HKU\Quirks\Environment /V TMP /T REG_EXPAND_SZ /D %USERPROFILE^%\AppData\Local\Temp /F REG.EXE DELETE HKU\Quirks\Environment /V ALLUSERSPROFILE /F REG.EXE DELETE HKU\Quirks\Environment /V APPDATA /F REG.EXE DELETE HKU\Quirks\Environment /V CommonAppData /F REG.EXE DELETE HKU\Quirks\Environment /V CommonProgramFiles /F REG.EXE DELETE HKU\Quirks\Environment /V CommonProgramFiles(x86) /F REG.EXE DELETE HKU\Quirks\Environment /V CommonProgramW6432 /F REG.EXE DELETE HKU\Quirks\Environment /V COMSPEC /F REG.EXE DELETE HKU\Quirks\Environment /V DriverData /F REG.EXE DELETE HKU\Quirks\Environment /V HOMEDRIVE /F REG.EXE DELETE HKU\Quirks\Environment /V HOMEPATH /F REG.EXE DELETE HKU\Quirks\Environment /V HOMESHARE /F REG.EXE DELETE HKU\Quirks\Environment /V LOCALAPPDATA /F REG.EXE DELETE HKU\Quirks\Environment /V OneDrive /F REG.EXE DELETE HKU\Quirks\Environment /V ProgramData /F REG.EXE DELETE HKU\Quirks\Environment /V ProgramFiles /F REG.EXE DELETE HKU\Quirks\Environment /V ProgramFiles(x86) /F REG.EXE DELETE HKU\Quirks\Environment /V ProgramW6432 /F REG.EXE DELETE HKU\Quirks\Environment /V PSModulePath /F REG.EXE DELETE HKU\Quirks\Environment /V PUBLIC /F REG.EXE DELETE HKU\Quirks\Environment /V SystemDrive /F REG.EXE DELETE HKU\Quirks\Environment /V SystemRoot /F REG.EXE DELETE HKU\Quirks\Environment /V USERPROFILE /F REG.EXE DELETE HKU\Quirks\Environment /V windir /F REG.EXE UNLOAD HKU\Quirks
The operation completed successfully. […] The operation completed successfully.Note: if your registry hive is still loaded under
HKEY_USERS\S-1-5-21-‹number›-‹number›-‹number›-‹number›
,
you or your administrator can repair the registry entries there.
Restore the (persistent) registry entries from the file
quirk1.reg
created in step 14.:
REG.EXE DELETE "HKEY_CURRENT_USER\Environment" /VA /F REG.EXE IMPORT quirk1.reg
The operation completed successfully. The operation completed successfully.
(Optional) If you are curious, execute the following command line to
set a registry entry with empty value for the user environment
variable SystemRoot
and signal the (graphical)
Shell that environment variables
have changed, then watch what happens when you start some
applications – log off and log on again after you have seen
enough havoc:
SETX.EXE SystemRoot ""
SUCCESS: Specified value was saved.Note: since the Shell won’t start any more, your administrator needs to load your registry hive, then remove the offending empty registry entry
SystemRoot
and finally
unload the registry hive before you can continue with step 18.:
REG.EXE LOAD HKU\Quirks "%SystemDrive%\Users\‹account›\NTUSER.DAT" REG.EXE DELETE HKU\Quirks\Environment /V SystemRoot /F REG.EXE UNLOAD HKU\Quirks
The operation completed successfully. The operation completed successfully. The operation completed successfully.Note: if your registry hive is still loaded under
HKEY_USERS\S-1-5-21-‹number›-‹number›-‹number›-‹number›
,
you or your administrator can repair the registry entries there.
Build the console application quirk1.exe
from the
source file quirk1.c
created in step 1. a
fifth time, now with the preprocessor macro QUIRKS
defined as 3:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=2 quirk1.c advapi32.lib kernel32.lib user32.lib userenv.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk1.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk1.exe quirk1.obj advapi32.lib kernel32.lib user32.lib userenv.lib
Export the registry entries from the registry key
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
to the file quirk1.reg
:
REG.EXE EXPORT "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" quirk1.reg /Y
The operation completed successfully.
Execute the console application quirk1.exe
built in
step 18. as administrator to show the second
bug a last time:
.\quirk1.exe
COMPUTERNAME=AMNESIAC LOGONSERVER=\\AMNESIAC NUMBER_OF_PROCESSORS=4 OS=Windows_NT Path=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\; PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE=AMD64 PROCESSOR_IDENTIFIER=Intel64 Family 6 Model 94 Stepping 3, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=5e03 SESSIONNAME=Console USERDOMAIN_ROAMINGPROFILE=AMNESIACOUCH⁴:
persistent
system
environment variables stored with empty value in the
Registry
are not written to the environment block, and they
inhibit the addition of programmatic system environment variables
with the same name to the environment block!
Oops³: the previously present environment
variables USERDOMAIN
and USERNAME
are
missing too!
(Optional) If you are curious, execute the following command line to
signal the Shell
Explorer.exe
that environment
variables have changed and see what happens when you start some
applications, then reboot:
SETX.EXE windir "" /M
SUCCESS: Specified value was saved.
Note: since Windows won’t start
any more, you need to boot
Windows RE, load
the registry hive C:\Windows\System32\Config\SYSTEM
,
restore the values of the overwritten registry entries, remove the
remaining empty registry entries and finally unload the registry
hive before you can boot Windows again:
REG.EXE LOAD "HKU\SYSTEM" "‹drive›:\Windows\System32\Config\SYSTEM"
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ComSpec /T REG_EXPAND_SZ /D %SystemRoot^%\System32\cmd.exe /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V DriverData /T REG_EXPAND_SZ /D %SystemRoot^%\System32\Drivers\DriverData /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V Path /T REG_EXPAND_SZ /D %SystemRoot^%\System32;%SystemRoot^%;%SystemRoot^%\System32\Wbem;%SystemRoot^%\System32\WindowsPowerShell\v1.0
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V PSModulePath /T REG_EXPAND_SZ /D %SystemRoot^%\System32\WindowsPowerShell\v1.0\Modules\ /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V TEMP /T REG_EXPAND_SZ /D %SystemRoot^%\Temp /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V TMP /T REG_EXPAND_SZ /D %SystemRoot^%\Temp /F
REG.EXE ADD "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V windir /T REG_EXPAND_SZ /D %SystemRoot^% /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ALLUSERSPROFILE /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V APPDATA /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V CommonAppData /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V CommonProgramFiles /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V CommonProgramFiles(x86) /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V CommonProgramW6432 /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V HOMEDRIVE /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V HOMEPATH /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V HOMESHARE /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V LOCALAPPDATA /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V OneDrive /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ProgramData /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ProgramFiles /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ProgramFiles(x86) /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V ProgramW6432 /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V PUBLIC /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V SystemDrive /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V SystemRoot /F
REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V USERPROFILE /F
REG.EXE UNLOAD "HKU\SYSTEM"
The operation completed successfully. […] The operation completed successfully.
Restore the registry entries from the file quirk1.reg
created in step 19.:
REG.EXE DELETE "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /VA /F REG.EXE IMPORT quirk1.reg
The operation completed successfully. The operation completed successfully.
(Optional) If you are curious, execute the following command line to
set a registry entry with empty value for the system environment
variable SystemRoot
and signal the (graphical)
Shell
Explorer.exe
that environment
variables have changed, then watch what happens when you start some
applications – log off and log on again or reboot after you
have seen enough havoc:
SETX.EXE SystemRoot "" /M
SUCCESS: Specified value was saved.
Note: since Windows won’t start
any more, you need to boot
Windows RE, load
the registry hive C:\Windows\System32\Config\SYSTEM
,
remove the offending empty registry entry SystemRoot
and finally unload the registry hive before you can boot
Windows again:
REG.EXE LOAD "HKU\SYSTEM" "‹drive›:\Windows\System32\Config\SYSTEM" REG.EXE DELETE "HKU\SYSTEM\ControlSet00‹digit›\Control\Session Manager\Environment" /V SystemRoot /F REG.EXE UNLOAD "HKU\SYSTEM"
The operation completed successfully. The operation completed successfully. The operation completed successfully.
Note: a proper implementation calls functions like
GetSystemDirectory()
,
GetSystemWindowsDirectory()
,
GetSystemWow64Directory()
,
GetWindowsDirectory()
,
SHGetFolderPath()
and
SHGetKnownFolderPath()
to determine (system) paths instead to evaluate (user-controlled)
environment variables!
Cmd.exe
, then execute the
following command lines to remove all environment variables and
(attempt to) start the applications
Explorer.exe
,
NotePad.exe
,
RegEdit.exe
and
Write.exe
as well as
IExplore.exe
and
WordPad.exe
afterwards:
REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de> CHDIR /D "%SystemRoot%" FOR /F "Delims==" %? IN ('SET') DO @SET "%?=" DIR Explorer.exe NotePad.exe RegEdit.exe Write.exe Win.ini /B Explorer.exe /E,/Separate,. NotePad.exe /P Win.ini RegEdit.exe /M Write.exe START IExplore.exe START WordPad.exe EXITNote: the command lines can be copied and pasted as block into a Command Processor window.
explorer.exe notepad.exe regedit.exe write.exe win.iniOops: while
NotePad.exe
and
RegEdit.exe
start properly,
Explorer.exe
,
Write.exe
plus
START IExplore.exe
fail silently, and
START WordPad.exe
fails with an error message box!
Create the text file quirk3.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL WINAPI _DllMainCRTStartup(HMODULE hModule,
DWORD dwReason,
LPVOID lpReserved)
{
WCHAR szModule[MAX_PATH];
DWORD dwModule;
WCHAR szProcess[MAX_PATH];
DWORD dwProcess;
WCHAR szMessage[1024];
if (dwReason != DLL_PROCESS_ATTACH)
return FALSE;
dwModule = GetModuleFileName(hModule,
szModule,
sizeof(szModule) / sizeof(*szModule));
if (dwModule < sizeof(szModule) / sizeof(*szModule))
szModule[dwModule] = L'\0';
dwProcess = GetModuleFileName((HMODULE) NULL,
szProcess,
sizeof(szProcess) / sizeof(*szProcess));
if (dwProcess < sizeof(szProcess) / sizeof(*szProcess))
szProcess[dwProcess] = L'\0';
if (wsprintf(szMessage,
L"\'%ls\' loaded at address 0x%p in process \'%ls\'\n",
szModule, hModule, szProcess) == 0)
return FALSE;
return IDOK == MessageBoxEx(HWND_DESKTOP,
szMessage,
__LPREFIX(__FUNCTION__) L"() entry point",
MB_OKCANCEL,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT));
}
MAKELANGID
MAKELANGID
Build the
DLL
quirk3.dll
from the source file quirk3.c
created in step 1.:
SET CL=/GAFy /LD /Oisy /W4 /Zl SET LINK=/NODEFAULTLIB CL.EXE quirk3.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk3.dll
is a pure
Win32
DLL
and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk3.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /NODEFAULTLIB /out:quirk3.dll /dll /implib:quirk3.lib quirk3.obj kernel32.lib user32.lib
Create the subdirectory System32\
in the current
directory, create hardlinks
NDFAPI.dll
and
PropSys.dll
of the
DLL
quirk3.dll
built in step 2. in this subdirectory,
set the environment variable SystemRoot
to the current
directory, then execute the two START
commands used
before again:
MKDIR System32 MKLINK /H System32\NDFAPI.dll quirk3.dll MKLINK /H System32\PropSys.dll quirk3.dll SET SystemRoot=%CD% START IExplore.exe START WordPad.exeNote: the command lines can be copied and pasted as block into a Command Processor window.
Hardlink created for System32\NPFAPI.dll <<===>> quirk3.dll Hardlink created for System32\PropSys.dll <<===>> quirk3.dllOUCH: the internal
Start
command of the Command Processor
Cmd.exe
loads and executes
(at least) arbitrary bogus
DLLs
named
NDFAPI.dll
and PropSys.dll
from
an arbitrary bogus system directory!
.bat
and .cmd
are
associated with the
file types
alias
Programmatic Identifiers
batfile
and cmdfile
whose
Open
verbis registered with the command line template
"%1" %*
:
ASSOC .bat ASSOC .cmd FTYPE batfile FTYPE cmdfile
.bat=batfile .cmd=cmdfile batfile="%1" %* cmdfile="%1" %*
ShellExecute()
and
ShellExecuteEx()
retrieve these command line templates, replace the various tokens
%‹digit›
,
%‹letter›
and %*
with file
or path names and arguments, then feed the completed command line to
one of the
CreateProcess()
,
CreateProcessAsUser()
,
CreateProcessWithLogonW()
or
CreateProcessWithTokenW()
functions.
Launching Applications (ShellExecute, ShellExecuteEx, SHELLEXECUTEINFO)
The documentation for the
CreateProcess()
function states:
To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file.Due to the security vulnerability CVE-2014-0315 alias MS14-019 I discovered and reported at the MSRC about 11 years ago, which was fixed with security update 2922229 some months later, the following note was added several years later:
Important
The MSRC engineering team advises against this. See MS14-019 – Fixing a binary hijacking via .cmd or .bat file for more details.
Create the text file quirk4.vbs
with the following
content in an arbitrary, preferable empty directory:
Rem Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("Scripting.FileSystemObject")
Const fsoWindowsFolder = 0
Const fsoSystemFolder = 1
Const fsoTemporaryFolder = 2
strFolder = .GetSpecialFolder(fsoWindowsFolder).Path
strProgram = .BuildPath(strFolder, "NotePad.exe")
End With
With WScript.CreateObject("WScript.Shell").Environment("VOLATILE")
If .Item("COMSPEC") = vbNullString Then
.Item("COMSPEC") = strProgram
Else
.Remove("COMSPEC")
End If
End With
Execute the
VBScript
quirk4.vbs
created in step 1. per double-click to
set the volatile environment variable COMSPEC
to the
path name of the Editor.
Create the text files quirk4.bat
and
quirk4.cmd
with the following content in an arbitrary,
preferable empty directory:
PAUSE
Execute the batch scripts quirk4.bat
and
quirk4.cmd
created in step 3. per double-click.
OUCH: the Editor
starts instead of the Command Processor
Cmd.exe
and displays an error
message box – most obviously Microsoft’s
developers and their quality miserability
assurance ignore their own companies’ documentation and
(security) guidelines!
Execute the
VBScript
quirk4.vbs
created in step 1. per double-click to
remove the volatile environment variable COMSPEC
again.
COMSPEC
pointing to an arbitrary Alternate Data Stream or an
arbitrary UNC path
\\‹computer›\‹share›\[‹directory›\[…\]]‹file›
is left as an exercise to the reader!
batfile
and cmdfile
associated with the extensions
.bat
and .cmd
from its default value
"%1" %*
to
C:\Windows\System32\Cmd.exe /D /C "%L" %*
:
FTYPE batfile=%COMSPEC% /D /C "%L" %* FTYPE cmdfile=%COMSPEC% /D /C "%L" %*Note: the internal
Ftype
command must be run with administrative privileges.
If you still have not abandonded the bad habit of
using the Protected Administrator account created
during Windows Setup you also need to change the
command line templates registered for the RunAs
verb:
REGEDIT4
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\batfile\Shell\Open\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C \"%L\" %*"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\batfile\Shell\RunAs\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C \"%L\" %*"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\cmdfile\Shell\Open\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C \"%L\" %*"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\cmdfile\Shell\RunAs\Command]
@="C:\\Windows\\System32\\Cmd.exe /D /C \"%L\" %*"
CAVEAT: thanksto the Merged View of HKEY_CLASSES_ROOT introduced with Windows 2000 this remediation but fails if the unnamed default registry entry with the default command line template is present in the corresponding registry key
HKEY_CURRENT_USER\Software\Classes\‹file type›\Shell\‹verb›\Command
of a user account!
Create the text file quirk4.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
const STARTUPINFO si = {sizeof(si)};
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
PROCESS_INFORMATION pi;
WCHAR szCmdLine[] = L".\\quirk4.bat";
WCHAR szProcess[MAX_PATH];
DWORD dwProcess = sizeof(szProcess) / sizeof(*szProcess);
DWORD dwThread;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!CreateProcess((LPCWSTR) NULL,
szCmdLine,
(LPSECURITY_ATTRIBUTES) NULL,
(LPSECURITY_ATTRIBUTES) NULL,
FALSE,
CREATE_DEFAULT_ERROR_MODE | CREATE_UNICODE_ENVIRONMENT,
(LPWSTR) NULL,
(LPCWSTR) NULL,
&si,
&pi))
PrintConsole(hConsole,
L"CreateProcess() returned error %lu\n",
dwError = GetLastError());
else
{
if (!QueryFullProcessImageName(pi.hProcess, 0, szProcess, &dwProcess))
PrintConsole(hConsole,
L"QueryFullProcessImageName() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"Child process loaded from image file \'%ls\'\n",
szProcess);
PrintConsole(hConsole,
L"Child process %lu with primary thread %lu created\n",
pi.dwProcessId, pi.dwThreadId);
if (WaitForSingleObject(pi.hThread, INFINITE) == WAIT_FAILED)
PrintConsole(hConsole,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!GetExitCodeThread(pi.hThread, &dwThread))
PrintConsole(hConsole,
L"GetExitCodeThread() returned error %lu\n",
dwError = GetLastError());
else
if (dwThread > 65535)
PrintConsole(hConsole,
L"Primary thread %lu of child process %lu exited with code 0x%08lX\n",
pi.dwThreadId, pi.dwProcessId, dwThread);
else
PrintConsole(hConsole,
L"Primary thread %lu of child process %lu exited with code %lu\n",
pi.dwThreadId, pi.dwProcessId, dwThread);
if (!CloseHandle(pi.hThread))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
PrintConsole(hConsole,
L"WaitForSingleObject() returned error %lu\n",
dwError = GetLastError());
if (!GetExitCodeProcess(pi.hProcess, &dwProcess))
PrintConsole(hConsole,
L"GetExitCodeProcess() returned error %lu\n",
dwError = GetLastError());
else
if (dwProcess > 65535)
PrintConsole(hConsole,
L"Child process %lu exited with code 0x%08lX\n",
pi.dwProcessId, dwProcess);
else
PrintConsole(hConsole,
L"Child process %lu exited with code %lu\n",
pi.dwProcessId, dwProcess);
if (!CloseHandle(pi.hProcess))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk4.exe
from the
source file quirk4.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk4.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk4.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk4.c quirk4.c(58) : warning C4090: 'function' : different 'const' qualifiers Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk4.exe quirk4.obj kernel32.lib user32.lib
Create the text file quirk4.bat
with the following
content next to the console application quirk4.exe
built in step 2.:
@ECHO %CMDCMDLINE%
Execute the console application quirk4.exe
built in
step 2. a first time to prove the documentation cited above
wrong:
.\quirk4.exe
Child process loaded from image file 'C:\Windows\SysWOW64\cmd.exe' Child process 6008 with primary thread 5092 created C:\Windows\system32\cmd.exe /c .\quirk4.bat Primary thread 5092 of child process 6008 exited with code 0 Child process 6008 exited with code 0OUCH¹: despite its lpApplicationName argument set to
NULL
, the Win32 function
CreateProcess()
executes a batch script with only its lpCommandLine argument
set to the (absolute or relative) path name of that batch script!
Set the environment variable COMSPEC
to the path name
of an arbitrary application, then execute the console application
quirk4.exe
a second time to show undocumented
behaviour:
SET COMSPEC=%SystemRoot%\System32\Reg.exe .\quirk4.exe
Child process loaded from image file 'C:\Windows\SysWOW64\reg.exe' Child process 5832 with primary thread 5704 created ERROR: Invalid Argument/Option - '/c'. Type "REG /?" for usage. Primary thread 5704 of child process 5832 exited with code 1 Child process 5832 exited with code 1OUCH²: the Win32 function
CreateProcess()
evaluates the environment variable COMSPEC
and executes
an arbitrary (hostile) application!
Set the environment variable COMSPEC
to the (relative
or absolute) path name of an arbitrary
NTFS
Alternate Data Stream
that contains an arbitrary application, then execute the console
application quirk4.exe
a third time to show
undocumented behaviour:
SET COMSPEC=.\quirk4.bat:Zone.Identifier TYPE "%SystemRoot%\System32\Reg.exe" 1>"%COMSPEC%" .\quirk4.exe
Child process loaded from image file 'C:\Users\Stefan\Desktop\quirk4.bat:Zone.Identifier' Child process 6252 with primary thread 6096 created ERROR: Invalid Argument/Option - '/c'. Type "REG /?" for usage. Primary thread 6096 of child process 6252 exited with code 1 Child process 6252 exited with code 1OUCH³: the Win32 function
CreateProcess()
evaluates the environment variable COMSPEC
and executes
an arbitrary (hostile) application also from an
Alternate Data Stream!
Remove the environment variable COMSPEC
, then execute
the console application quirk4.exe
a fourth time to
show undocumented behaviour:
SET COMSPEC= .\quirk4.exe
Child process loaded from image file 'C:\Windows\SysWOW64\cmd.exe' Child process 5436 with primary thread 5916 created C:\Windows\system32\cmd.exe /c .\quirk4.bat Primary thread 5916 of child process 5436 exited with code 0 Child process 5436 exited with code 0Oops: the Win32 function
CreateProcess()
finds the Command Processor even when
the environment variable COMSPEC
is not set!
Copy an arbitrary application (or an empty file) as
CMD.EXE
into the current directory, then execute the
console application quirk4.exe
a fifth time to show
that it doesn’t search the path, i.e. it doesn’t execute
CMD.EXE
from its application directory
or the
current directory:
COPY "%SystemRoot%\System32\Reg.exe" CMD.EXE .\quirk4.exe
Child process loaded from image file 'C:\Windows\SysWOW64\cmd.exe' Child process 5720 with primary thread 6184 created C:\Windows\system32\cmd.exe /c .\quirk4.bat Primary thread 6184 of child process 5720 exited with code 0 Child process 5720 exited with code 0
Create the subdirectory System32\
in the current
directory, copy an arbitrary application as CMD.EXE
into it and set the environment variable SystemRoot
to
the current directory, then execute the console application
quirk4.exe
a last time to show more undocumented
behaviour:
MKDIR System32 MOVE CMD.EXE System32 SET SystemRoot=%CD% .\quirk4.exe
Child process loaded from image file 'C:\Users\Stefan\Desktop\System32\CMD.EXE' Child process 5372 with primary thread 5448 created ERROR: Invalid Argument/Option - '/c'. Type "REG /?" for usage. Primary thread 5448 of child process 5372 exited with code 1 Child process 5372 exited with code 1OUCH⁴: if the environment variable
COMSPEC
is not set the Win32 function
CreateProcess()
evaluates the environment variable SystemRoot
and
executes an arbitrary (hostile) application from
the path %SystemRoot%\System32\Cmd.exe
!
COMSPEC
or SystemRoot
pointing to an arbitrary
UNC path is left as
an exercise to the reader!
Note: an exploration of the (mis)behaviour with the
Win32 functions
CreateProcessAsUser()
,
CreateProcessWithLogonW()
and
CreateProcessWithTokenW()
is left as an exercise to the reader.
Note: a repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.
COMSPEC
or
SystemRoot
is a well-known weakness,
documented as
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
and
CWE-73: External Control of File Name or Path
in the
CWE™;
it allows well-known attacks like
CAPEC-13: Subverting Environment Variable Values
and
CAPEC-471: Search Order Hijacking
documented in the
CAPEC™.
If the specified process is the System process or one of the Client
Server Run-Time Subsystem (CSRSS) processes, this function fails and
the last error code is ERROR_ACCESS_DENIED
because
their access restrictions prevent user-level code from opening them.
The highlighted parts are but wrong!
The WOW64 emulator runs in user mode. It provides an interface between the 32-bit version of Ntdll.dll and the kernel of the processor, and it intercepts kernel calls. The WOW64 emulator consists of the following DLLs:These DLLs, along with the 64-bit version of Ntdll.dll, are the only 64-bit binaries that can be loaded into a 32-bit process. On Windows 10 on ARM, CHPE (Compiled Hybrid Portable Executable) binaries may also be loaded into an x86 32-bit process.
- Wow64.dll provides the core emulation infrastructure and the thunks for the Ntoskrnl.exe entry-point functions.
- Wow64Win.dll provides thunks for the Win32k.sys entry-point functions.
- (x64 only) Wow64Cpu.dll provides support for running x86 programs on x64.
- (Intel Itanium only) IA32Exec.bin contains the x86 software emulator.
- (Intel Itanium only) Wowia32x.dll provides the interface between IA32Exec.bin and WOW64.
- (ARM64 only) xtajit.dll contains the x86 software emulator.
- (ARM64 only) wowarmw.dll provides support for running ARM32 programs on ARM64.
At startup, Wow64.dll loads the x86 version of Ntdll.dll (or the CHPE version, if enabled) and runs its initialization code, which loads all necessary 32-bit DLLs. […]
NTDLL.dll
deems necessary.
Create the text file quirk8.c
with the following
content in an arbitrary, preferable empty directory:
// Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
int main(void)
{
return 'VOID';
}
Note: this trivial minimal program has 0 (in words:
zero) dependencies, i.e. the initialisation code of
NTDLL.dll
has no
reason to load any other
DLL
at all!
Build the 32-bit console application quirk8.exe
from
the source file quirk8.c
created in step 1.:
SET CL=/W4 /X /Zl SET LINK=/ENTRY:main /NODEFAULTLIB CL.EXE quirk8.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk8.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:main /NODEFAULTLIB /out:quirk8.exe quirk8.objFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Execute the 32-bit console application quirk8.exe
built
in step 2. under the 32-bit debugger
CDB.exe
:
CDB.EXE /C q /G /g .\quirk8.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk8.exe Symbol search path is: srv* Executable search path is: ModLoad: 01230000 01232000 image01230000 ModLoad: 779c0000 77b40000 ntdll.dll ModLoad: 75610000 75720000 C:\Windows\syswow64\kernel32.dll ModLoad: 752d0000 75317000 C:\Windows\syswow64\KERNELBASE.dll ModLoad: 75540000 755e1000 C:\Windows\syswow64\ADVAPI32.DLL ModLoad: 757a0000 7584c000 C:\Windows\syswow64\msvcrt.dll ModLoad: 77420000 77439000 C:\Windows\SysWOW64\sechost.dll ModLoad: 77300000 773f0000 C:\Windows\syswow64\RPCRT4.dll ModLoad: 750e0000 75140000 C:\Windows\syswow64\SspiCli.dll ModLoad: 750d0000 750dc000 C:\Windows\syswow64\CRYPTBASE.dllOOPS¹: 8 (in words: eight) not explicitly referenced and thus neither necessary nor required DLLs are loaded in
Caveat: on 64-bit systems, a 32-bit debugger
doesn’t receive LOAD_DLL_DEBUG_EVENT
and
UNLOAD_DLL_DEBUG_EVENT
events for 64-bit
DLLs.
Execute the 32-bit console application quirk8.exe
built
in step 2. under the 64-bit debugger
CDB.exe
:
CDB.EXE /C q /G /g .\quirk8.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk8.exe Symbol search path is: srv* Executable search path is: ModLoad: 00000000`12340000 00000000`12342000 image00000000`12340000 ModLoad: 00000000`77800000 00000000`7799f000 ntdll.dll ModLoad: 00000000`779c0000 00000000`77b40000 ntdll32.dll ModLoad: 00000000`75040000 00000000`7507f000 C:\Windows\SYSTEM32\wow64.dll ModLoad: 00000000`74fe0000 00000000`7503c000 C:\Windows\SYSTEM32\wow64win.dll ModLoad: 00000000`74fd0000 00000000`74fd8000 C:\Windows\SYSTEM32\wow64cpu.dll ModLoad: 00000000`775e0000 00000000`776ff000 WOW64_IMAGE_SECTION ModLoad: 00000000`75610000 00000000`75720000 WOW64_IMAGE_SECTION ModLoad: 00000000`775e0000 00000000`776ff000 NOT_AN_IMAGE ModLoad: 00000000`77700000 00000000`777fb000 NOT_AN_IMAGE ModLoad: 00000000`75610000 00000000`75720000 C:\Windows\syswow64\kernel32.dll ModLoad: 00000000`752d0000 00000000`75317000 C:\Windows\syswow64\KERNELBASE.dll ModLoad: 00000000`75540000 00000000`755e1000 C:\Windows\syswow64\ADVAPI32.DLL ModLoad: 00000000`757a0000 00000000`7584c000 C:\Windows\syswow64\msvcrt.dll ModLoad: 00000000`77420000 00000000`77439000 C:\Windows\SysWOW64\sechost.dll ModLoad: 00000000`77300000 00000000`773f0000 C:\Windows\syswow64\RPCRT4.dll ModLoad: 00000000`750e0000 00000000`75140000 C:\Windows\syswow64\SspiCli.dll ModLoad: 00000000`750d0000 00000000`750dc000 C:\Windows\syswow64\CRYPTBASE.dllOOPS²: as before!
Build the 64-bit console application quirk8.exe
from
the source file quirk8.c
created in step 1.:
SET CL=/W4 /X /Zl SET LINK=/ENTRY:main /NODEFAULTLIB CL.EXE quirk8.c
Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01 for x64 Copyright (C) Microsoft Corporation. All rights reserved. quirk8.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:main /NODEFAULTLIB /out:quirk8.exe quirk8.obj
Execute the 64-bit console application quirk8.exe
built
in step 5. under the 64-bit debugger
CDB.exe
:
CDB.EXE /C q /G /g .\quirk8.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 AMD64 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk8.exe Symbol search path is: srv* Executable search path is: ModLoad: 00000000`12340000 00000000`12342000 image00000000`12340000 ModLoad: 00000000`77800000 00000000`7799f000 ntdll.dll ModLoad: 00000000`775e0000 00000000`776ff000 C:\Windows\system32\kernel32.dll ModLoad: 000007fe`fd510000 000007fe`fd577000 C:\Windows\system32\KERNELBASE.dll ModLoad: 000007fe`fd980000 000007fe`fda5b000 C:\Windows\system32\ADVAPI32.dll ModLoad: 000007fe`fe1c0000 000007fe`fe25f000 C:\Windows\system32\msvcrt.dll ModLoad: 000007fe`fe260000 000007fe`fe27f000 C:\Windows\SYSTEM32\sechost.dll ModLoad: 000007fe`ff250000 000007fe`ff37c000 C:\Windows\system32\RPCRT4.dllOOPS³: 6 (in words: six) not explicitly referenced and thus neither necessary nor required DLLs are loaded in
Finally (try to) execute the 64-bit console application
quirk8.exe
built in step 5. under the 32-bit
debugger CDB.exe
:
CDB.EXE /C q /G /g .\quirk8.exe
Microsoft (R) Windows Debugger Version 6.1.7601.17514 X86 Copyright (c) Microsoft Corporation. All rights reserved. CommandLine: .\quirk8.exe Cannot execute '.\quirk8.exe', Win32 error 0n50 "The request is not supported." Debuggee initialization failed, Win32 error 0n50 "The request is not supported."
GetOpenClipboardWindow()
,
GetWindow()
and
GetWindowModuleFileName()
are documented in the
MSDN as
follows:
Retrieves the handle to the window that currently has the clipboard open.[…]HWND GetOpenClipboardWindow();
If the function succeeds, the return value is the handle to the window that has the clipboard open. If no window has the clipboard open, the return value is NULL. To get extended error information, call GetLastError.
Retrieves a handle to a window that has the specified relationship (Z-Order or owner) to the specified window.HWND GetWindow( HWND hWnd, UINT uCmd );
[…]
Value Meaning … … GW_ENABLEDPOPUP
6The retrieved handle identifies the enabled popup window owned by the specified window (the search uses the first such window found using GW_HWNDNEXT); otherwise, if there are no enabled popup windows, the retrieved handle is that of the specified window. […]
If the function succeeds, the return value is a window handle. If no window exists with the specified relationship to the specified window, the return value is NULL. To get extended error information, call GetLastError.
Retrieves the full path and file name of the module associated with the specified window handle.Note: the last documentation fails to specify what[…]UINT GetWindowModuleFileName( HWND hwnd, LPTSTR pszFileName, UINT cchFileNameMax );
The return value is the total number of characters copied into the buffer.
associated modulemeans as well as error conditions and restrictions! 228469
Create the text file quirk14.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szBuffer[MAX_PATH];
DWORD dwBuffer;
DWORD dwError = ERROR_SUCCESS;
HWND hWindow = HWND_DESKTOP;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
SetLastError(123456789);
hWindow = GetActiveWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetActiveWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
hWindow = GetConsoleWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetConsoleWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
SetLastError(123456789);
hWindow = GetDesktopWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetDesktopWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
hWindow = GetForegroundWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetForegroundWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
SetLastError(123456789);
hWindow = GetOpenClipboardWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetOpenClipboardWindow() returned error %lu\n",
dwError = GetLastError());
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
hWindow = GetShellWindow();
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetShellWindow() returned NULL\n");
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
hWindow = GetTopWindow(HWND_DESKTOP);
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetTopWindow() returned error %lu\n",
dwError = GetLastError());
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
SetLastError(123456789);
hWindow = GetWindow(hWindow, GW_ENABLEDPOPUP);
if (hWindow == NULL)
PrintConsole(hConsole,
L"GetWindow() returned error %lu\n",
dwError = GetLastError());
else
{
dwBuffer = GetWindowModuleFileName(hWindow,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
}
dwBuffer = GetWindowModuleFileName(hWindow = HWND_BOTTOM,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
dwBuffer = GetWindowModuleFileName(hWindow = HWND_TOP,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
dwBuffer = GetWindowModuleFileName(hWindow = HWND_TOPMOST,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
dwBuffer = GetWindowModuleFileName(hWindow = HWND_NOTOPMOST,
szBuffer,
sizeof(szBuffer) / sizeof(*szBuffer));
if (dwBuffer == 0)
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned error %lu\n",
hWindow, dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowModuleFileName(0x%p, …) returned pathname \'%ls\' of %lu characters\n",
hWindow, szBuffer, dwBuffer);
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk14.exe
from the
source file quirk14.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk14.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk14.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk14.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk14.exe quirk14.obj kernel32.lib user32.lib
Execute the console application quirk14.exe
built in
step 2. to demonstrate the inconsistent (mis)behaviour:
.\quirk14.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
GetWindowModuleFileName(0x00000000, …) returned error 1400 GetWindow() returned error 1400 GetActiveWindow() returned NULL GetWindowModuleFileName(0x0025021C, …) returned pathname 'C:\Users\Stefan\Desktop\quirk14.exe' of 35 characters GetWindowModuleFileName(0x00010010, …) returned error 123456789 GetWindowModuleFileName(0x0025021C, …) returned pathname 'C:\Users\Stefan\Desktop\quirk14.exe' of 35 characters GetOpenClipboardWindow() returned error 123456789 GetWindowModuleFileName(0x00020104, …) returned error 123456789 GetWindowModuleFileName(0x035D0EA0, …) returned pathname 'C:\Users\Stefan\Desktop\quirk14.exe' of 35 characters GetWindow() returned error 123456789 GetWindowModuleFileName(0x00000001, …) returned error 1400 GetWindowModuleFileName(0x00000000, …) returned error 1400 GetWindowModuleFileName(0xFFFFFFFF, …) returned error 1400 GetWindowModuleFileName(0xFFFFFFFE, …) returned error 1400 0x578 (WIN32: 1400 ERROR_INVALID_WINDOW_HANDLE) -- 1400 (1400) Error message text: Invalid window handle. CertUtil: -error command completed successfully.OUCH¹: the Win32 function
GetWindow()
does not support the pseudo handle
HWND_DESKTOP
alias NULL
, but returns
NULL
with Win32 error code 1400 alias
ERROR_INVALID_WINDOW_HANDLE
set!
OUCH²: it does not return
the handle of the specified window if this does not own an enabled
popup window, but NULL
and fails to set the
Win32 error code!
OUCH³: it returns NULL
but fails
to set the Win32 error code if no window with the
specified relationship exists!
Note: the Win32 function
GetWindowModuleFileName()
returns 0 and does not modify the buffer in case of
failure!
OUCH⁴: it returns the path name of the calling console application although this did not create the window!
OUCH⁵: it returns 0 but fails to set the Win32 error code if the window was created in another process, except for the console window!
OUCH⁶: it does not support
the pseudo handles
HWND_BOTTOM
,
HWND_TOP
alias HWND_DESKTOP
,
HWND_TOPMOST
and
HWND_NOTOPMOST
,
but returns 0 with Win32 error code 1400 alias
ERROR_INVALID_WINDOW_HANDLE
set!
OUCH⁷: the Win32 function
GetOpenClipboardWindow()
returns NULL
but fails to set the Win32
error if the clipboard is not opened by a window!
An access control list (ACL) is a list of access control entries (ACE). Each ACE in an ACL identifies a trustee and specifies the access rights allowed, denied, or audited for that trustee. The security descriptor for a securable object can contain two types of ACLs: a DACL and a SACL.Note: no document denotes different access rights for deletion of directories than for deletion of files, which both are filesystem objects and children of a parent filesystem object, i.e. deletion should be governed byA discretionary access control list (DACL) identifies the trustees that are allowed or denied access to a securable object. When a process tries to access a securable object, the system checks the ACEs in the object's DACL to determine whether to grant access to it. If the object does not have a DACL, the system grants full access to everyone. If the object's DACL has no ACEs, the system denies all attempts to access the object because the DACL does not allow any access rights. The system checks the ACEs in sequence until it finds one or more ACEs that allow all the requested access rights, or until any of the requested access rights are denied. […]
FILE_DELETE_CHILD
of the parent (too)!
The TechNet article How Permissions Work specifies:
Folder permissions include Full Control, Modify, Read & Execute, List Folder Contents, Read, and Write. Each of these permissions consists of a logical group of special permissions that are listed and defined in the following table.The MSDN article File Security and Access Rights but contradicts:Permissions for Files and Folders
Permission Description … … Delete Subfolders and Files Allows or denies deleting subfolders and files, even if the Delete permission has not been granted on the subfolder or file. (Applies to folders.) Delete Allows or denies deleting the file or folder. If you do not have Delete permission on a file or folder, you can still delete it if you have been granted Delete Subfolders and Files on the parent folder. […]
You should also be aware of the following:
Groups or users that are granted Full Control on a folder can delete any files in that folder, regardless of the permissions protecting the file.
The valid access rights for files and directories include the DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, and SYNCHRONIZE standard access rights.The MSDN articles SACL Access Right and Requesting Access Rights to an Object specify:[…]
By default, authorization for access to a file or directory is controlled strictly by the ACLs in the security descriptor associated with that file or directory. In particular, the security descriptor of a parent directory is not used to control access to any child file or directory.
The ACCESS_SYSTEM_SECURITY access right is not valid in a DACL because DACLs do not control access to a SACL.
Contrary to the last two (highlighted) statements, the documentation as well as the synopsis of Windows’Note
The MAXIMUM_ALLOWED constant cannot be used in an ACE.
Icacls
command line program but state:
Displays or modifies discretionary access control lists (DACLs) on specified files, and applies stored DACLs to files in specified directories.Oops: the parameter[…]
[…]icacls ‹FileName› [/grant[:r] ‹Sid›:‹Perm›[…]] [/deny ‹Sid›:‹Perm›[…]] [/remove[:g|:d]] ‹Sid›[…]] [/t] [/c] [/l] [/q] [/setintegritylevel ‹Level›:‹Policy›[…]]
Perm is a permission mask that can be specified in one of the following forms:
A sequence of simple rights:
[…]
A comma-separated list in parenthesis of specific rights:
D (delete)
RC (read control)
WDAC (write DAC)
WO (write owner)
S (synchronize)
AS (access system security)
MA (maximum allowed)
[…]
RD (read data/list directory)
WD (write data/add file)
AD (append data/add subdirectory)
[…]
DC (delete child)
/Inheritance:{E|D|R}
is undocumented!
Command-Line Reference
Cmd.exe
, then execute the
following command lines to prove the documentation cited above
wrong, and also show some undocumented (mis)behaviour:
REM Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de> CHDIR /D "%TMP%" COPY NUL: Quirk16f.tmp ECHO Step 1: remove all inherited access permissions from file 'Quirk16f.tmp' ICACLS.EXE Quirk16f.tmp /Inheritance:R CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp ECHO Step 2: (attempt to) add inheritable access permissions to file 'Quirk16f.tmp' ICACLS.EXE Quirk16f.tmp /Grant *S-1-3-3:(CI)(D) /Grant *S-1-3-2:(OI)(S) /Grant *S-1-3-1:(CI)(IO)(RC) /Grant *S-1-3-0:(OI)(IO)(WDAC) CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp ECHO Step 3: add access permissions to file 'Quirk16f.tmp' ICACLS.EXE Quirk16f.tmp /Deny *S-1-3-4:(AS) /Grant *S-1-3-4:(MA) /Grant *S-1-3-3:(D) /Grant *S-1-3-2:(RC) /Grant *S-1-3-1:(S) /Grant *S-1-3-0:(WDAC) CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp ECHO Step 4: remove access permissions from file 'Quirk16f.tmp' ICACLS.EXE Quirk16f.tmp /Remove:g "%USERNAME%" /Remove:g None CACLS.EXE Quirk16f.tmp /S ICACLS.EXE Quirk16f.tmp ECHO Step 5: delete file 'Quirk16f.tmp' ERASE Quirk16f.tmpNote: the command lines can be copied and pasted as block into a Command Processor window.
1 file(s) copied. Step 1: remove all inherited access permissions from file 'Quirk16f.tmp' processed file: Quirk16f.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp "D:PAI" Quirk16f.tmp Successfully processed 1 files; Failed processing 0 files Step 2: (attempt to) add inheritable access permissions to file 'Quirk16f.tmp' processed file: Quirk16f.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp "D:PAI" Quirk16f.tmp Successfully processed 1 files; Failed processing 0 files Step 3: add access permissions to file 'Quirk16f.tmp' processed file: Quirk16f.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp "D:PAI(D;;;;;OW)(A;;WD;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;0x100000;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;RC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;0x110000;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;;;;OW)" Quirk16f.tmp OWNER RIGHTS:(DENY)(S) AMNESIAC\Stefan:(WDAC) AMNESIAC\None:(S) AMNESIAC\Stefan:(Rc) AMNESIAC\None:(D) OWNER RIGHTS: Successfully processed 1 files; Failed processing 0 files Step 4: remove access permissions from file 'Quirk16f.tmp' processed file: Quirk16f.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16f.tmp Access denied Quirk16f.tmp: Access denied Successfully processed 0 files; Failed processing 1 files Step 5: delete file 'Quirk16f.tmp'Note:
ICACLs.exe
does
not add inheritable
ACEs to files, but
discards them silently, reporting success.
Ouch¹: ICACLs.exe
converts its access permissions
AS
alias
ACCESS_SYSTEM_SECURITY
and
MA
alias
MAXIMUM_ALLOWED
to 0 alias
NO_ACCESS
!
Ouch²: ICACLs.exe
converts its access permission D
into
the compound access mask
SD
alias
DELETE
plus
SYNCHRONIZE
, and displays this
compound access mask 0x110000
as its
access permission D
too!
Note: ICACLs.exe
converts the
well-known SIDs
S-1-3-0
alias
CREATOR OWNER
,
S-1-3-1
alias
CREATOR GROUP
,
S-1-3-2
alias
CREATOR OWNER SERVER
, and
S-1-3-3
alias
CREATOR GROUP SERVER
into the
object’s effective user and (primary) group
SIDs.
Ouch³: ICACLs.exe
displays the access mask 0 alias
NO_ACCESS
of the
ACE
(D;;;;;OW)
as (S)
alias
SYNCHRONIZE
!
Note: both ICACLs.exe
and
CACLs.exe
fail (expected)
when the ACE
(D;;;;;OW)
with access mask 0 alias
NO_ACCESS
is present in the
DACL
– it overrides the implicit
RC
alias
READ_CONTROL
and
WD
alias
WRITE_DAC
access rights granted to
the object’s owner and has the same effect as the
ACE
(A;;;;;OW)
.
The TechNet article Security Identifiers Technical Overview specifies:
The following table lists the universal well-known SIDs.AD DS: Owner RightsUniversal well-known SIDs
Value Universal Well-Known SID Identifies … … … S-1-3-4 Owner Rights A group that represents the current owner of the object. When an ACE that carries this SID is applied to an object, the system ignores the implicit READ_CONTROL and WRITE_DAC permissions for the object owner.
MKDIR Quirk16d.tmp ECHO Step A: remove all inherited access permissions from directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Inheritance:R CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp ECHO Step B: add (inheritable) access permissions to directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Deny *S-1-3-4:(AS,MA) /Grant *S-1-3-3:(CI)(RC,WDAC) /Grant *S-1-3-2:(OI)(RD,WD) /Grant *S-1-3-1:(CI)(IO)(AS) /Grant *S-1-3-0:(OI)(IO)(MA) CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp ECHO Step C: (attempt to) delete directory 'Quirk16d.tmp' RMDIR Quirk16d.tmp ECHO Step D: remove all inheritable access permissions from directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Remove:g *S-1-3-3 /Remove:g *S-1-3-2 /Remove:g *S-1-3-1 /Remove:g *S-1-3-0 CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp ECHO Step E: create file 'Quirk16d.tmp\Quirk16f.tmp' COPY NUL: Quirk16d.tmp\Quirk16f.tmp ECHO Step F: display access permissions of file 'Quirk16d.tmp\Quirk16f.tmp' ICACLS.EXE Quirk16d.tmp\Quirk16f.tmp CACLS.EXE Quirk16d.tmp\Quirk16f.tmp /S ECHO Step G: (attempt to) delete file 'Quirk16d.tmp\Quirk16f.tmp' ERASE Quirk16d.tmp\Quirk16f.tmp ECHO Step H: add access permissions to directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Grant *S-1-3-1:(AD) /Grant *S-1-3-0:(S) CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp CACLS.EXE Quirk16d.tmp\Quirk16f.tmp /S ECHO Step I: delete file 'Quirk16d.tmp\Quirk16f.tmp' ERASE Quirk16d.tmp\Quirk16f.tmp ECHO Step J: remove access permissions from directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Remove:g "%USERNAME%" CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp ECHO Step K: create subdirectory 'Quirk16d.tmp\Quirk16d.tmp' MKDIR Quirk16d.tmp\Quirk16d.tmp ECHO Step L: delete subdirectory 'Quirk16d.tmp\Quirk16d.tmp' RMDIR Quirk16d.tmp\Quirk16d.tmp ECHO Step M: (attempt to) delete directory 'Quirk16d.tmp' RMDIR Quirk16d.tmp ECHO Step N: modify access permissions of directory 'Quirk16d.tmp' ICACLS.EXE Quirk16d.tmp /Grant *S-1-3-0:(S) /Remove:g None CACLS.EXE Quirk16d.tmp /S ICACLS.EXE Quirk16d.tmp ECHO Step O: delete directory 'Quirk16d.tmp' RMDIR Quirk16d.tmpNote: the command lines can be copied and pasted as block into a Command Processor window.
Step A: remove all inherited access permissions from directory 'Quirk16d.tmp' processed file: Quirk16d.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI" Quirk16d.tmp Successfully processed 1 files; Failed processing 0 files Step B: add (inheritable) access permissions to directory 'Quirk16d.tmp' processed file: Quirk16d.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(D;;;;;OW)(A;OIIO;0x2000000;;;CO)(A;;CCDC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;OIIO;CCDC;;;S-1-3-2)(A;CIIO;0x1000000;;;CG)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)(A;CIIO;RCWD;;;S-1-3-3)" Quirk16d.tmp OWNER RIGHTS:(DENY)(S) CREATOR OWNER:(OI)(IO)(MA) AMNESIAC\Stefan:(RD,WD) CREATOR OWNER SERVER:(OI)(IO)(RD,WD) CREATOR GROUP:(CI)(IO)(AS) AMNESIAC\None:(Rc,WDAC) CREATOR GROUP SERVER:(CI)(IO)(Rc,WDAC) Successfully processed 1 files; Failed processing 0 files Step C: (attempt to) delete directory 'Quirk16d.tmp' Access denied Step D: remove all inheritable access permissions from directory 'Quirk16d.tmp' processed file: Quirk16d.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(A;;CCDC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)" Quirk16d.tmp AMNESIAC\Stefan:(RD,WD,AD) AMNESIAC\None:(Rc,WDAC) Successfully processed 1 files; Failed processing 0 files Step E: create file 'Quirk16d.tmp\Quirk16f.tmp' 1 file(s) copied. Step F: display access permissions of file 'Quirk16d.tmp\Quirk16f.tmp' Quirk16d.tmp\Quirk16f.tmp AMNESIAC\Stefan:(F) NT AUTHORITY\SYSTEM:(F) The mapping between account names and security IDs was done. (RX) Successfully processed 1 files; Failed processing 0 files Access denied Step G: (attempt to) delete file 'Quirk16d.tmp\Quirk16f.tmp' Could Not Find C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp\Quirk16f.tmp Step H: add access permissions to directory 'Quirk16d.tmp' processed file: Quirk16d.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(D;;;;;OW)(A;;0x100000;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;LC;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;CCDC;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)" Quirk16d.tmp OWNER RIGHTS:(DENY)(S) AMNESIAC\Stefan:(AD) AMNESIAC\None:(S) AMNESIAC\Stefan:(RD,WD) AMNESIAC\None:(Rc,WDAC) Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp\Quirk16f.tmp "D:AI(A;;FA;;;S-1-5-21-820728443-44925810-1835867902-1000)(A;;FA;;;SY)(A;;0x1200a9;;;S-1-5-5-0-231840)" Step I: delete file 'Quirk16d.tmp\Quirk16f.tmp' Step J: remove access permissions from directory 'Quirk16d.tmp' processed file: Quirk16d.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp "D:PAI(D;;;;;OW)(A;;LC;;;S-1-5-21-820728443-44925810-1835867902-513)(A;;RCWD;;;S-1-5-21-820728443-44925810-1835867902-513)" Quirk16d.tmp OWNER RIGHTS:(DENY)(S) AMNESIAC\None:(S) AMNESIAC\None:(Rc,WDAC) Successfully processed 1 files; Failed processing 0 files Step K: create subdirectory 'Quirk16d.tmp\Quirk16d.tmp' Step L: delete subdirectory 'Quirk16d.tmp\Quirk16d.tmp' Step M: (attempt to) delete directory 'Quirk16d.tmp' Access denied Step N: modify access permissions of directory 'Quirk16d.tmp' processed file: Quirk16d.tmp Successfully processed 1 files; Failed processing 0 files C:\Users\Stefan\AppData\Local\Temp\Quirk16d.tmp Access denied Quirk16d.tmp: Access denied Successfully processed 0 files; Failed processing 1 files Step O: delete directory 'Quirk16d.tmp'Ouch⁴:
ICACLs.exe
adds
inheritable ACEs
with the invalid access masks
AS
alias
ACCESS_SYSTEM_SECURITY
and
MA
alias
MAXIMUM_ALLOWED
to
DACLs!
Note: without an inheritable ACE from its parent object the default security descriptor from the process’s access token is applied to a new object.
Ouch⁵: both
CACLs.exe
and the internal
Del
alias
Erase
command of the Command Processor
Cmd.exe
fail without
SYNCHRONIZE
access permission to
the parent directory of the filesystem object to access!
Note: both ICACLs.exe
and
CACLs.exe
fail (expected)
when the ACE
(D;;;;;OW)
with access mask 0 alias
NO_ACCESS
is present in the
DACL
– it overrides the implicit
RC
alias
READ_CONTROL
and
WD
alias
WRITE_DAC
access rights granted to
the object’s owner and has the same effect as the
ACE
(A;;;;;OW)
.
Ouch⁶: the internal
Rd
alias
Rmdir
command of the Command Processor
succeeds despite the missing SD
alias DELETE
access permission!
DELETE
access permission as well as
to deny (un)intentionally any access to (filesystem) objects via the
ACCESS_SYSTEM_SECURITY
and
MAXIMUM_ALLOWED
access permissions!
They replied with the following statements:
Thank you for your submission. We determined your finding does not meet our bar for immediate servicing. For more information, please see the Microsoft Security Servicing Criteria for Windows (https://aka.ms/windowscriteria).However, we’ve marked your finding for future review as an opportunity to improve our products. I do not have a timeline for this review and will not provide updates moving forward. As no further action is required at this time, I am closing this case. You will not receive further correspondence regarding this submission.
CreateDirectory()
and
CreateFile()
are documented in the
MSDN as
follows:
Creates a new directory. If the underlying file system supports security on files and directories, the function applies a specified security descriptor to the new directory.[…]
[…]BOOL CreateDirectory( LPCTSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes );
lpSecurityAttributes
A pointer to a SECURITY_ATTRIBUTES structure. The lpSecurityDescriptor member of the structure specifies a security descriptor for the new directory. […]
Creates or opens a file or I/O device. […]The[…]BOOL CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile );
lpSecurityAttributes
A pointer to a SECURITY_ATTRIBUTES structure that contains two separate but related data members: an optional security descriptor, […]
The lpSecurityDescriptor member of the structure specifies a SECURITY_DESCRIPTOR for a file or device. […]
SECURITY_ATTRIBUTES
and
SECURITY_DESCRIPTOR
structures are documented as follows:
[…]typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
lpSecurityAttributes
A pointer to a SECURITY_DESCRIPTOR structure that controls access to the object. […]
A security descriptor includes information that specifies the following components of an object's security:File Management Functions Directory Management Functions The Win32 functions
- An owner security identifier (SID)
- A primary group SID
- A discretionary access control list (DACL)
- A system access control list (SACL)
- Qualifiers for the preceding items
DeleteFile()
,
DeleteFileTransacted()
,
MoveFileEx()
,
MoveFileWithProgress()
,
RemoveDirectory()
and
ReplaceFile()
are documented in the
MSDN as
follows:
Deletes an existing file.[…]
[…]BOOL DeleteFile( LPCTSTR lpFileName );
lpFileName
The name of the file to be deleted.
[…]
- To delete or rename a file, you must have either delete permission on the file, or delete child permission in the parent directory.
- To delete or rename a file, you must have either delete permission on the file, or delete child permission in the parent directory.
Moves an existing file or directory, including its children, with various move options.Oops: the highlighted sentences above and below but contradict the linked MSDN article File Security and Access Rights by 5÷1![…]
[…]BOOL MoveFileEx( LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, DWORD dwFlags );
lpExistingFileName
The current name of the file or directory on the local computer.
[…]
lpNewFileName
The new name of the file or directory on the local computer.
[…]
- To delete or rename a file, you must have either delete permission on the file or delete child permission in the parent directory. If you set up a directory with all access except delete and delete child and the ACLs of new files are inherited, then you should be able to create a file without being able to delete it. However, you can then create a file, and get all the access you request on the handle that is returned to you at the time that you create the file. If you request delete permission at the time you create the file, you can delete or rename the file with that handle but not with any other handle. For more information, see File Security and Access Rights.
Moves a file or directory, including its children. You can provide a callback function that receives progress notifications.[…]
[…]BOOL MoveFileWithProgress( LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, LPPROGRESS_ROUTINE lpProgressRoutine, LPVOID lpData, DWORD dwFlags );
lpExistingFileName
The current name of the file or directory on the local computer.
[…]
lpNewFileName
The new name of the file or directory on the local computer.
[…]
- To delete or rename a file, you must have either delete permission on the file or delete child permission in the parent directory. If you set up a directory with all access except delete and delete child and the ACLs of new files are inherited, then you should be able to create a file without being able to delete it. However, you can then create a file, and you will get all the access you request on the handle returned to you at the time you create the file. If you requested delete permission at the time you created the file, you could delete or rename the file with that handle but not with any other.
Deletes an existing empty directory.[…]
[…]BOOL RemoveDirectory( LPCTSTR lpPathName );
lpPathName
The path of the directory to be removed. This path must specify an empty directory, and the calling process must have delete access to the directory.
RemoveDirectory()
but fails to delete empty directories created with the same access
rights as files!
- To delete or rename a file, you must have either delete permission on the file or delete child permission in the parent directory. If you set up a directory with all access except delete and delete child and the DACLs of new files are inherited, then you should be able to create a file without being able to delete it. However, you can then create a file, and you will get all the access you request on the handle returned to you at the time you create the file. If you requested delete permission at the time you created the file, you could delete or rename the file with that handle but not with any other.
Create the text file quirk17.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <sddl.h>
#include <aclapi.h>
#ifndef _WIN64
#pragma intrinsic(memcmp)
#else
#pragma function(memcmp)
int memcmp(char const *left, char const *right, size_t count)
{
size_t index;
int delta;
for (index = 0; index < count; index++)
if (delta = left[index] - right[index])
return delta;
return 0;
}
#endif
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
typedef struct _ace
{
ACE_HEADER Header;
ACCESS_MASK Mask;
SID Trustee;
} ACE;
const struct _acl
{
ACL acl;
ACE ace;
} acl = {{ACL_REVISION, 0, sizeof(acl), 1, 0},
#if 0 // (A;NP;FA;;;AU)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
FILE_ALL_ACCESS,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_AUTHENTICATED_USER_RID}}},
#else // (A;NP;SD;;;OW)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
DELETE,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}},
#endif
dacl = {{ACL_REVISION, 0, sizeof(dacl), 1, 0},
#ifndef QUIRKS // (A;NP;0x1f0000;;;OW)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
STANDARD_RIGHTS_ALL,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_CREATOR_SID_AUTHORITY, SECURITY_CREATOR_OWNER_RIGHTS_RID}}},
#elif QUIRKS == 0 // (A;NP;FA;;;AU)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
FILE_ALL_ACCESS,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_AUTHENTICATED_USER_RID}}},
#elif QUIRKS == 1 // (A;NP;0x1f0000;;;S-1-5-15)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
STANDARD_RIGHTS_ALL,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_THIS_ORGANIZATION_RID}}},
#elif QUIRKS == 2 // (A;NP;FA;;;PS)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
FILE_ALL_ACCESS,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_PRINCIPAL_SELF_RID}}},
#elif QUIRKS == 3 // (A;NP;0x1f0000;;;S-1-5-1000)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
STANDARD_RIGHTS_ALL,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_OTHER_ORGANIZATION_RID}}},
#elif QUIRKS == 4 // (A;NP;0x3000000;;;IU)
{{ACCESS_ALLOWED_ACE_TYPE, NO_PROPAGATE_INHERIT_ACE, sizeof(ACE)},
ACCESS_SYSTEM_SECURITY | MAXIMUM_ALLOWED,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_NT_AUTHORITY, SECURITY_INTERACTIVE_RID}}},
#else // NOTE: construct an INVALID DACL!
{{SYSTEM_MANDATORY_LABEL_ACE_TYPE, 0, sizeof(ACE)},
SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}},
#endif
sacl = {{ACL_REVISION, 0, sizeof(sacl), 1, 0},
// (ML;;NRNWNX;;;ME)
{{SYSTEM_MANDATORY_LABEL_ACE_TYPE, 0, sizeof(ACE)},
SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP | SYSTEM_MANDATORY_LABEL_NO_READ_UP | SYSTEM_MANDATORY_LABEL_NO_WRITE_UP,
{SID_REVISION, ANYSIZE_ARRAY, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID}}};
const SECURITY_DESCRIPTOR sd = {SECURITY_DESCRIPTOR_REVISION,
0,
#ifdef QUIRKS
SE_DACL_PRESENT | SE_DACL_PROTECTED,
#else // BUG: CreateFile*() and CreateDirectory*() fail with ERROR_PRIVILEGE_NOT_HELD when a "mandatory label" is present!
SE_DACL_PRESENT | SE_DACL_PROTECTED | SE_SACL_PRESENT | SE_SACL_PROTECTED,
#endif
(SID *) NULL,
(SID *) NULL,
#ifdef QUIRKS
(ACL *) NULL,
#else
&sacl,
#endif
&dacl};
const SECURITY_ATTRIBUTES sa = {sizeof(sa),
&sd,
FALSE};
const WCHAR szName[] = L"Quirk17.tmp";
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
SECURITY_DESCRIPTOR *lpSD;
ACL *lpDACL;
LPWSTR lpSDDL;
DWORD dwError;
DWORD dwFile;
HANDLE hFile;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(&sd,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
(LPDWORD) NULL))
PrintConsole(hConsole,
L"ConvertSecurityDescriptorToStringSecurityDescriptor() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"Using security descriptor \'%ls\' to create file and directory \'%ls\'\n",
lpSDDL, szName);
if (LocalFree(lpSDDL) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
hFile = CreateFile(szName,
FILE_WRITE_DATA | READ_CONTROL,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
&sa,
CREATE_NEW,
#if 0
FILE_FLAG_DELETE_ON_CLOSE,
#else
FILE_ATTRIBUTE_NORMAL,
#endif
(HANDLE) NULL);
if (hFile == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"CreateFile() returned error %lu\n",
dwError = GetLastError());
else
{
dwError = GetSecurityInfo(hFile,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
&lpDACL,
(ACL **) NULL,
&lpSD);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"GetSecurityInfo() returned error %lu\n",
dwError);
else
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
(LPDWORD) NULL))
PrintConsole(hConsole,
L"ConvertSecurityDescriptorToStringSecurityDescriptor() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"File \'%ls\' created with security descriptor \'%ls\'\n",
szName, lpSDDL);
if (LocalFree(lpSDDL) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
if (memcmp(lpDACL, &dacl, sizeof(dacl)) != 0)
PrintConsole(hConsole,
L"DACL of file differs from original DACL!\n");
if (LocalFree(lpSD) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
if (!WriteFile(hFile,
L"\xFEFF", // UTF-16LE byte order mark
sizeof(L'\xFEFF'),
&dwFile,
(LPOVERLAPPED) NULL))
PrintConsole(hConsole,
L"WriteFile() returned error %lu\n",
dwError = GetLastError());
else
if (dwFile != sizeof(L'\xFEFF'))
PrintConsole(hConsole,
L"WriteFile() failed, %lu of %lu bytes written\n",
dwFile, sizeof(L'\xFEFF'));
if (!CloseHandle(hFile))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
if (!DeleteFile(szName))
{
PrintConsole(hConsole,
L"DeleteFile() returned error %lu\n",
dwError = GetLastError());
if (dwError == ERROR_ACCESS_DENIED)
{
dwError = SetNamedSecurityInfo(szName,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
(SID *) NULL,
(SID *) NULL,
&acl,
(ACL *) NULL);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"SetNamedSecurityInfo() returned error %lu\n",
dwError);
else
if (!DeleteFile(szName))
PrintConsole(hConsole,
L"DeleteFile() returned error %lu\n",
dwError = GetLastError());
dwError = ERROR_ACCESS_DENIED;
}
}
}
if (!CreateDirectory(szName,
&sa))
PrintConsole(hConsole,
L"CreateDirectory() returned error %lu\n",
dwError = GetLastError());
else
{
dwError = GetNamedSecurityInfo(szName,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
(SID **) NULL,
(SID **) NULL,
&lpDACL,
(ACL **) NULL,
&lpSD);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"GetNamedSecurityInfo() returned error %lu\n",
dwError);
else
{
if (!ConvertSecurityDescriptorToStringSecurityDescriptor(lpSD,
SDDL_REVISION_1,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION,
&lpSDDL,
(LPDWORD) NULL))
PrintConsole(hConsole,
L"ConvertSecurityDescriptorToStringSecurityDescriptor() returned error %lu\n",
dwError = GetLastError());
else
{
PrintConsole(hConsole,
L"Directory \'%ls\' created with security descriptor \'%ls\'\n",
szName, lpSDDL);
if (LocalFree(lpSDDL) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
if (memcmp(lpDACL, &dacl, sizeof(dacl)) != 0)
PrintConsole(hConsole,
L"DACL of directory differs from original DACL!\n");
if (LocalFree(lpSD) != NULL)
PrintConsole(hConsole,
L"LocalFree() returned error %lu\n",
dwError = GetLastError());
}
if (!RemoveDirectory(szName))
{
PrintConsole(hConsole,
L"RemoveDirectory() returned error %lu\n",
dwError = GetLastError());
if (dwError == ERROR_ACCESS_DENIED)
{
dwError = SetNamedSecurityInfo(szName,
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
(SID *) NULL,
(SID *) NULL,
&acl,
(ACL *) NULL);
if (dwError != ERROR_SUCCESS)
PrintConsole(hConsole,
L"SetNamedSecurityInfo() returned error %lu\n",
dwError);
else
if (!RemoveDirectory(szName))
PrintConsole(hConsole,
L"RemoveDirectory() returned error %lu\n",
dwError = GetLastError());
dwError = ERROR_ACCESS_DENIED;
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk17.exe
from the
source file quirk17.c
created in step 1., with the
preprocessor macro QUIRKS
defined as 0 or 1:
SET CL=/GAFy /Oisy /W4 /wd4090 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE /DQUIRKS=0 quirk17.c advapi32.lib kernel32.lib user32.lib/D (Preprocessor Definitions) For details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk17.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk17.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk17.exe quirk17.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk17.exe
built in
step 2. to verify its proper function:
.\quirk17.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Using security descriptor 'D:P(A;NP;FA;;;AU)' to create file and directory 'Quirk17.tmp' File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;AU)' Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;AU)' 0x0 (WIN32: 0 ERROR_SUCCESS) -- 0 (0) Error message text: The operation completed successfully. CertUtil: -error command completed successfully.A file as well as a directory created (for example) with only the DACL
D:P
(A;NP;FA;;;AU)
or
D:P
(A;NP;0x1f0000;;;S-1-5-15)
can be deleted.
Build the console application quirk17.exe
a second time
from the source file quirk17.c
created in step 1.,
now with the preprocessor macro QUIRKS
defined as 2 or
3:
CL.EXE /DQUIRKS=2 quirk17.c advapi32.lib kernel32.lib user32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk17.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk17.exe quirk17.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk17.exe
built in
step 4. to show the misbehaviour:
.\quirk17.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Using security descriptor 'D:P(A;NP;FA;;;PS)' to create file and directory 'Quirk17.tmp'
File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;PS)'
Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;FA;;;PS)'
RemoveDirectory() returned error 5
0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5)
Error message text: Access denied.
CertUtil: -error command completed successfully.
OUCH: while a file created (for example) with only
the
DACL
D:P
(A;NP;FA;;;PS)
or
D:P
(A;NP;0x1f0000;;;S-1-5-1000)
can be deleted, a directory created with only this
DACL
can’t be deleted!
Build the console application quirk17.exe
a third time
from the source file quirk17.c
created in step 1.,
now with the preprocessor macro QUIRKS
defined as 4:
CL.EXE /DQUIRKS=4 quirk17.c advapi32.lib kernel32.lib user32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk17.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk17.exe quirk17.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk17.exe
built in
step 6. to show a second misbehaviour:
.\quirk17.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Using security descriptor 'D:P(A;NP;0x3000000;;;IU)' to create file and directory 'Quirk17.tmp' File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;;;;IU)' DACL of file differs from original DACL! Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P(A;NP;;;;IU)' DACL of directory differs from original DACL! RemoveDirectory() returned error 5 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH: the Win32 functions
ConvertSecurityDescriptorToStringSecurityDescriptor()
,
CreateDirectory()
and
CreateFile()
fail to detect the (intentionally) invalid
DACL
D:P
(A;NP;0x3000000;;;IU)
– the latter functions apply a
DACL
D:P
(A;NP;;;;IU)
instead, granting only implicit
(A;NP;WDRC;;;OW)
access for the object’s owner!
Build the console application quirk17.exe
a fourth time
from the source file quirk17.c
created in step 1.,
now with the preprocessor macro QUIRKS
defined as 5:
CL.EXE /DQUIRKS=5 quirk17.c advapi32.lib kernel32.lib user32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk17.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk17.exe quirk17.obj advapi32.lib kernel32.lib user32.lib
Execute the console application quirk17.exe
built in
step 8. to show a third misbehaviour:
.\quirk17.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
ConvertSecurityDescriptorToStringSecurityDescriptor() returned error 1336 File 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P' DACL of file differs from original DACL! Directory 'Quirk17.tmp' created with security descriptor 'O:S-1-5-21-820728443-44925810-1835867902-1000G:S-1-5-21-820728443-44925810-1835867902-513D:P' DACL of directory differs from original DACL! RemoveDirectory() returned error 5 0x5 (WIN32: 5 ERROR_ACCESS_DENIED) -- 5 (5) Error message text: Access denied. CertUtil: -error command completed successfully.OUCH: while the Win32 function
ConvertSecurityDescriptorToStringSecurityDescriptor()
now properly detects the (intentionally) invalid
DACL
D:P
(ML;;NXNRNW;;;ME)
and returns the Win32 error code 1336 alias
ERROR_INVALID_ACL
,
both
CreateDirectory()
and
CreateFile()
fail to detect it and apply the empty
DACL
D:P
instead,
again granting only implicit
(A;NP;WDRC;;;OW)
access for the object’s owner!
Use the Four Defined Normalization Forms:
The Unicode Consortium has defined four normalization forms: NFC (form C), NFD (form D), NFKC (form KC), and NFKD (form KD). Each form eliminates some differences but preserves case. Win32 and the .NET Framework support all four normalization forms.[…]
The NLS enumeration type NORM_FORM supports the four standard Unicode normalization forms. Forms C and D provide canonical forms for strings. Non-canonical forms KC and KD provide further compatibility, and can reveal certain semantic equivalences that are not apparent in forms C and D. However, they do so at the expense of a certain loss of information, and generally should not be used as a canonical way to store strings.
Of the two canonical forms, form C is a "composed" form and form D is a "decomposed" form. For example, form C uses the single Unicode code point "Ä" (U+00C4), while form D uses ("A" + "¨", that is U+0041 U+0308). These render identically, because "¨" (U+0308) is a combining character. Form D can use any number of code points to represent a single code point used by form C.
If two strings are identical in either form C or form D, they are identical in the other form. Furthermore, when correctly rendered, they display indistinguishably from one another and from the original non-normalized string.
Create the
UTF-16LE
encoded text file
quirk29.txt
containing the extended characters of
Code Page 1252
in form C in the first line and in form D in the second line in an
arbitrary, preferable empty directory:
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Note: with the leading
Byte Order Mark
U+FEFF
, the
intermediate and the trailing
CR/LF
pairs the file quirk29.txt
contains 309
Unicode
characters in 618 bytes.
Display the file quirk29.txt
using the internal
Type
command:
TYPE quirk29c.txt
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ €‚ƒ„…†‡ˆ‰S□‹ŒZ□‘’“”•–—˜™s□›œz□Y□ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿A□A□A□A□A□A□ÆC□E□E□E□E□I□I□I□I□ÐN□O□O□O□O□O□×ØU□U□U□U□Y□Þßa□a□a□a□a□a□æc□e□e□e□e□i□i□i□i□ðn□o□o□o□o□o□÷øu□u□u□u□y□þy□OUCH: contrary to the highlighted statements of the documentation cited above, normalisation form D is not correctly rendered – all (58) decomposed characters with combining code points are displayed wrong using 2 glyphs!
Create the
UTF-16LE
NFC
encoded text file
quirk29c.txt
containing the extended characters of
Code Page 1252
in an arbitrary, preferable empty directory:
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Note: with the leading
Byte Order Mark
U+FEFF
and the
trailing
CR/LF
pair the file quirk29c.txt
contains 126
Unicode
characters in 252 bytes.
Display the file quirk29c.txt
using the internal
Type
command:
TYPE quirk29c.txt
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Create the
UTF-16LE
NFD
encoded text file
quirk29d.txt
containing the extended characters of
Code Page 1252
in the same directory:
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
Note: with the leading
Byte Order Mark
U+FEFF
and the
trailing
CR/LF
the file quirk29d.txt
contains 184
Unicode
characters in 368 bytes.
Display the file quirk29d.txt
using the internal
Type
command:
TYPE quirk29d.txt
€‚ƒ„…†‡ˆ‰S□‹ŒZ□‘’“”•–—˜™s□›œz□Y□ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿A□A□A□A□A□A□ÆC□E□E□E□E□I□I□I□I□ÐN□O□O□O□O□O□×ØU□U□U□U□Y□Þßa□a□a□a□a□a□æc□e□e□e□e□i□i□i□i□ðn□o□o□o□o□o□÷øu□u□u□u□y□þy□OUCH: contrary to the highlighted statements of the documentation cited above, normalisation form D is not correctly rendered – all (58) decomposed characters with combining code points are displayed wrong using 2 glyphs!
Create the text file quirk29.c
with the following
content:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define CP1252 L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\n"
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
UINT uiANSI;
CHAR szANSI[333];
WCHAR szWide[222];
INT niWide;
DWORD dwError = ERROR_SUCCESS;
DWORD dwConsole;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (!IsNormalizedString(NormalizationC,
CP1252, sizeof(CP1252) / sizeof(*CP1252)))
PrintConsole(hConsole,
L"IsNormalizedString() returned error %lu\n",
dwError = GetLastError());
if (!WriteConsole(hConsole, CP1252, sizeof(CP1252) / sizeof(*CP1252) - 1, &dwConsole, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else if (dwConsole != sizeof(CP1252) / sizeof(*CP1252) - 1)
PrintConsole(hConsole,
L"WriteConsole() wrote %lu of %lu wide characters\n",
dwConsole, sizeof(CP1252) / sizeof(*CP1252) - 1);
niWide = NormalizeString(NormalizationD,
CP1252, sizeof(CP1252) / sizeof(*CP1252),
szWide, sizeof(szWide) / sizeof(*szWide));
if (--niWide < 0)
PrintConsole(hConsole,
L"NormalizeString() returned error %lu\n",
dwError = GetLastError());
else
if (!WriteConsole(hConsole, szWide, niWide, &dwConsole, NULL))
PrintConsole(hConsole,
L"WriteConsole() returned error %lu\n",
dwError = GetLastError());
else if (dwConsole != niWide)
PrintConsole(hConsole,
L"WriteConsole() wrote %lu of %lu wide characters\n",
dwConsole, niWide);
if ((GetACP() == CP_UTF8)
&& (GetOEMCP() == CP_UTF8))
{
uiANSI = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
szWide, niWide,
szANSI, sizeof(szANSI),
(LPCCH) NULL, (LPBOOL) NULL);
if (uiANSI == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu\n",
dwError = GetLastError());
else
{
if (!WriteConsoleA(hConsole, szANSI, uiANSI, &dwConsole, NULL))
PrintConsole(hConsole,
L"WriteConsoleA() returned error %lu\n",
dwError = GetLastError());
else if (dwConsole != uiANSI)
PrintConsole(hConsole,
L"WriteConsoleA() wrote %lu of %lu characters\n",
dwConsole, uiANSI);
if (!WriteFile(hConsole, szANSI, uiANSI, &dwConsole, (LPOVERLAPPED) NULL))
PrintConsole(hConsole,
L"WriteFile() returned error %lu\n",
dwError = GetLastError());
else if (dwConsole != uiANSI)
PrintConsole(hConsole,
L"WriteFile() wrote %lu of %lu characters\n",
dwConsole, uiANSI);
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk29.exe
from the
source file quirk29.c
created in step 5.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk29.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk29.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk29.c quirk29.c(76) : warning C4389: '!=' : signed/unsigned mismatch Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk29.exe quirk29.obj kernel32.lib user32.lib
Execute the console application quirk29.exe
built in
step 6. to demonstrate the (mis)behaviour:
.\quirk29.exe
€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ €‚ƒ„…†‡ˆ‰S□‹ŒZ□‘’“”•–—˜™s□›œz□Y□ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿A□A□A□A□A□A□ÆC□E□E□E□E□I□I□I□I□ÐN□O□O□O□O□O□×ØU□U□U□U□Y□Þßa□a□a□a□a□a□æc□e□e□e□e□i□i□i□i□ðn□o□o□o□o□o□÷øu□u□u□u□y□þy□OUCH: contrary to the highlighted statements of the documentation cited above, normalisation form D is not correctly rendered – all (58) decomposed characters with combining code points are displayed wrong using 2 glyphs!
SetConsoleTitleW()
,
WriteConsoleOutputW()
and
WriteConsoleOutputCharacterW()
is left as an exercise to the reader.
Note: an exploration of the (mis)behaviour (and
bugs) with the Win32 functions
SetConsoleTitleA()
,
WriteConsoleA()
,
WriteConsoleOutputA()
,
WriteConsoleOutputCharacterA()
and
WriteFile()
when the system’s (global)
ANSI
and OEM
code pages are set to 65001 alias CP_UTF8
is also left
as an exercise to the reader.
Note: an exploration of the
behaviour with (low-level) Win32 functions like
DrawText()
,
DrawTextEx()
,
ExtTextOut()
,
GrayString()
,
PolyTextOut()
,
TabbedTextOut()
TabbedTextOut()
and
TextOut()
or (high-level) Win32 functions like
CreateWindowEx()
,
MessageBox()
,
MessageBoxEx()
,
MessageBoxIndirect()
,
MessageBoxTimeout()
,
SetConsoleTitle()
and
SetWindowText()
is also left as an exercise to the reader.
Note: a repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.
Aforms of the Win32 functions:
As of Windows Version 1903 (May 2019 Update), you can use the ActiveCodePage property in the appxmanifest for packaged apps, or the fusion manifest for unpackaged apps, to force a process to use UTF-8 as the process code page.The Win32 function[…]
Win32 APIs often support both -A and -W variants.
-A variants recognize the ANSI code page configured on the system and support
char*
, while -W variants operate in UTF-16 and supportWCHAR
.Until recently, Windows has emphasized "Unicode" -W variants over -A APIs. However, recent releases have used the ANSI code page and -A APIs as a means to introduce UTF-8 support to apps. If the ANSI code page is configured for UTF-8, -A APIs typically operate in UTF-8. This model has the benefit of supporting existing code built with -A APIs without any code changes.
[…]
Note
CP_ACP
equates toCP_UTF8
only if running on Windows Version 1903 (May 2019 Update) or above and the ActiveCodePage property described above is set to UTF-8. Otherwise, it honors the legacy system code page. We recommend usingCP_UTF8
explicitly.
GetLocaleInfo()
is documented in the
MSDN as
follows:
Retrieves information about a locale specified by identifier.Oops: contrary to the highlighted note, in Windows 10 1903 and later versions the[…]
Note For global compatibility, the application should prefer the Unicode "W" API forms to the "A" forms. GetLocaleInfoA will limit the character data and could result in results that appear corrupted to users, particularly in globally enabled applications. For this API, GetLocaleInfoEx is preferred as it is Unicode and also supports modern locale name standards.[…]int GetLocaleInfo( LCID Locale, LCTYPE LCType, LPSTR lpLCData, int cchData );
Locale
Locale Identifier for which to retrieve information. […]
LCType
The locale information to retrieve. For detailed definitions, see the LCType parameter of GetLocaleInfoEx.
lpLCData
Pointer to a buffer in which this function retrieves the requested locale information. This pointer is not used if cchData is set to 0. For more information, see the Remarks section.
cchData
Size, in TCHAR values, of the data buffer indicated by lpLCData. Alternatively, the application can set this parameter to 0. In this case, the function does not use the lpLCData parameter and returns the required buffer size, including the terminating null character.
[…]
Remarks
For the operation of this function, see Remarks for GetLocaleInfoEx.
[…]
Aform of this function supports but Unicode too!
Unfortunately the Remarks
section is empty and refers to the
GetLocaleInfoEx()
function, which is documented in the
MSDN as
follows:
Retrieves information about a locale specified by name.Oops: this function supports Unicode but only in UTF-16LE encoding, not in UTF-8 encoding, as theNote The application should call this function in preference to GetLocaleInfo if designed to run only on Windows Vista and later.[…][…]int GetLocaleInfoEx( LPCWSTR lpLocaleName LCTYPE LCType, LPSTR lpLCData, int cchData );
lpLocaleName
Pointer to a locale name, or one of the following predefined values. […]
LCType
The locale information to retrieve. For possible values, see the "Constants Used in the LCType Parameter of GetLocaleInfo, GetLocaleInfoEx, and SetLocaleInfo" section in Locale Information Constants. […]
lpLCData
Pointer to a buffer in which this function retrieves the requested locale information. This pointer is not used if cchData is set to 0.
cchData
Size, in characters, of the data buffer indicated by lpLCData. Alternatively, the application can set this parameter to 0. In this case, the function does not use the lpLCData parameter and returns the required buffer size, including the terminating null character.
[…]
GetLocaleInfoA()
function does in Windows 10 1903 and later versions!
The various
Locale Information Constants,
for example
LOCALE_SNATIVE*
,
are documented in the
MSDN as
follows:
Ouch: including a terminating
Value Meaning LOCALE_SNATIVECOUNTRYNAME Windows 7 and later: Native name of the country/region, for example, Españafor Spain. The maximum number of characters allowed for this string is 80, including a terminating null character.… … LOCALE_SNATIVEDIGITS Native equivalents of ASCII 0 through 9. The maximum number of characters allowed for this string is eleven, including a terminating null character. For example, Arabic uses ٠١٢٣٤٥٦٧٨٩. See also LOCALE_IDIGITSUBSTITUTION.… … LOCALE_SNATIVELANGUAGENAME Windows 7 and later: Native name of the language, for example, Հայերենfor Armenian (Armenia). The maximum number of characters allowed for this string is 80, including a terminating null character.
NUL
, the
UTF-8
encoded character string
u"٠١٢٣٤٥٦٧٨٩"
alias
"\xD9\xA0\xD9\xA1\xD9\xA2\xD9\xA3\xD9\xA4\xD9\xA5\xD9\xA6\xD9\xA7\xD9\xA8\xD9\xA9"
occupies but 21 instead of 11 characters, i.e. the second
highlighted statement of the documentation cited above is
definitely wrong!
Create the text file quirk35.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define ERROR_INVALID_LOCALE ERROR_INVALID_PARAMETER
#define LANG_MAX 0x01FF
#define SUBLANG_MAX 0x3F
#ifndef _WIN64
#pragma intrinsic(memcmp)
#else
#pragma function(memcmp)
int memcmp(char const *left, char const *right, size_t count)
{
size_t index;
int delta;
for (index = 0; index < count; index++)
if (delta = left[index] - right[index])
return delta;
return 0;
}
#endif
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
DWORD dwPrimary;
DWORD dwSubLang;
LCID lcLocale;
CHAR szANSI[11];
WCHAR szUnicode[11];
INT iInfo;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if ((GetACP() != CP_UTF8)
|| (GetOEMCP() != CP_UTF8))
for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
{
lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
if (GetLocaleInfoW(lcLocale,
LOCALE_SNATIVEDIGITS,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode)) == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
#if 0
if (wmemcmp(szUnicode, L"0123456789", sizeof("0123456789")) != 0)
#else
if (memcmp(szUnicode, L"0123456789", sizeof(L"0123456789")) != 0)
#endif
PrintConsole(hConsole,
L"0x%08lX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX U+%04hX\n",
lcLocale, szUnicode[0], szUnicode[1], szUnicode[2], szUnicode[3], szUnicode[4], szUnicode[5], szUnicode[6], szUnicode[7], szUnicode[8], szUnicode[9]);
if (GetLocaleInfoA(lcLocale,
LOCALE_SNATIVEDIGITS,
szANSI,
sizeof(szANSI)) == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
if (memcmp(szANSI, "0123456789", sizeof("0123456789")) != 0)
PrintConsole(hConsole,
L"0x%08lX %hs\n",
lcLocale, szANSI);
}
else
for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
{
lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
if (GetLocaleInfoA(lcLocale, LOCALE_ILANGUAGE, (LPSTR) NULL, 0) == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
dwError, L"LOCALE_ILANGUAGE", lcLocale);
}
else
{
iInfo = GetLocaleInfoA(lcLocale, LOCALE_SNATIVECOUNTRYNAME, (LPSTR) NULL, 0);
if (iInfo == 0)
PrintConsole(hConsole,
L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
dwError = GetLastError(), L"LOCALE_SNATIVECOUNTRYNAME", lcLocale);
else if (iInfo > 80)
PrintConsole(hConsole,
L"Character count %d returned for %ls of LCID 0x%08lX exceeds %d!\n",
iInfo, L"LOCALE_SNATIVECOUNTRYNAME", lcLocale, 80);
iInfo = GetLocaleInfoA(lcLocale, LOCALE_SNATIVEDIGITS, (LPSTR) NULL, 0);
if (iInfo == 0)
PrintConsole(hConsole,
L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
dwError = GetLastError(), L"LOCALE_SNATIVEDIGITS", lcLocale);
else if (iInfo > 11)
PrintConsole(hConsole,
L"Character count %d returned for %ls of LCID 0x%08lX exceeds %d!\n",
iInfo, L"LOCALE_SNATIVEDIGITS", lcLocale, 11);
iInfo = GetLocaleInfoA(lcLocale, LOCALE_SNATIVELANGUAGENAME, (LPSTR) NULL, 0);
if (iInfo == 0)
PrintConsole(hConsole,
L"GetLocaleInfo() returned error %lu for %ls of LCID 0x%08lX\n",
dwError = GetLastError(), L"LOCALE_SNATIVELANGUAGENAME", lcLocale);
else if (iInfo > 80)
PrintConsole(hConsole,
L"Character count %d returned for %ls of LCID 0x%08lX exceeds %d!\n",
iInfo, L"LOCALE_SNATIVELANGUAGENAME", lcLocale, 80);
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
LOCALE_ALL
LOCALE_ALLOW_NEUTRAL
LOCALE_ALLOW_NEUTRAL_NAMES
LOCALE_ALTERNATE_SORTS
LOCALE_CUSTOM*
LOCALE_FONTSIGNATURE
LOCALE_ICALENDARTYPE
LOCALE_ICENTURY
LOCALE_ICOUNTRY
LOCALE_ICURRDIGITS
LOCALE_ICURRENCY
LOCALE_IDATE
LOCALE_IDAYLZERO
LOCALE_IDEFAULT*
LOCALE_IDIGITS
LOCALE_IDIGITSUBSTITUTION
LOCALE_IFIRSTDAYOFWEEK
LOCALE_IFIRSTWEEKOFYEAR
LOCALE_IGEOID
LOCALE_IINTLCURRDIGITS
LOCALE_ILANGUAGE
LOCALE_ILDATE
LOCALE_ILZERO
LOCALE_IMEASURE
LOCALE_IMONLZERO
LOCALE_INEG*
LOCALE_INEGATIVEPERCENT
LOCALE_INEUTRAL
LOCALE_INVARIANT
LOCALE_IOPTIONALCALENDAR
LOCALE_IPAPERSIZE
LOCALE_IPOSITIVEPERCENT
LOCALE_IPOS*
LOCALE_IREADINGLAYOUT
LOCALE_ITIME
LOCALE_ITIMEMARKPOSN
LOCALE_ITLZERO
LOCALE_NAME*
LOCALE_NEUTRAL
LOCALE_NEUTRALDATA
LOCALE_NOUSEROVERRIDE
LOCALE_REPLACEMENT
LOCALE_RETURN*
LOCALE_S1159
LOCALE_S2359
LOCALE_SABBREV*
LOCALE_SCONSOLEFALLBACKNAME
LOCALE_SCOUNTRY
LOCALE_SCURRENCY
LOCALE_SDATE
LOCALE_SDAYNAME*
LOCALE_SDECIMAL
LOCALE_SDURATION
LOCALE_SENG*
LOCALE_SENGLISH*
LOCALE_SGROUPING
LOCALE_SIETFLANGUAGE
LOCALE_SINTLSYMBOL
LOCALE_SISO*
LOCALE_SKEYBOARDSTOINSTALL
LOCALE_SLANGDISPLAYNAME
LOCALE_SLANGUAGE
LOCALE_SLIST
LOCALE_SLOCALIZED*
LOCALE_SLONGDATE
LOCALE_SMON*
LOCALE_SMONTHDAY
LOCALE_SMONTHNAME*
LOCALE_SNAME
LOCALE_SNAN
LOCALE_SNATIVE*
LOCALE_SNEGATIVESIGN
LOCALE_SNEGINFINITY
LOCALE_SOPENTYPELANGUAGETAG
LOCALE_SORTNAME
LOCALE_SPARENT
LOCALE_SPECIFICDATA
LOCALE_SPERCENT
LOCALE_SPERMILLE
LOCALE_SPOSINFINITY
LOCALE_SPOSITIVESIGN
LOCALE_SSCRIPTS
LOCALE_SSHORTDATE
LOCALE_SSHORTESTDAYNAME*
LOCALE_SSHORTTIME
LOCALE_SSORT*
LOCALE_STHOUSAND
LOCALE_STIME*
LOCALE_SUPPLEMENTAL
LOCALE_SYEARMONTH
LOCALE_SYSTEM_DEFAULT
LOCALE_USE_CP_ACP
LOCALE_USER_DEFAULT
LOCALE_WINDOWS
LOCALE_NAME_MAX_LENGTH
MAKELANGID
MAKELANGID
MAKELCID
MAKELCID
Code Pages
Code Page Identifiers
Code Page Identifiers
EnumLocalesProcEx()
EnumLocalesProcEx()
EnumSystemLocalesEx()
EnumSystemLocalesEx()
GetACP()
GetACP()
GetLocaleInfo()
GetLocaleInfo()
GetOEMCP()
GetOEMCP()
IsValidLocale()
IsValidLocale()
IsValidLocaleName()
IsValidLocaleName()
ResolveLocaleName()
SetLocaleInfo()
SetLocaleInfo()
SetThreadLocale()
SetThreadLocale()
Build the console application quirk35.exe
from the
source file quirk35.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk35.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk35.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk35.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk35.exe quirk35.obj kernel32.lib user32.lib
Execute the console application quirk35.exe
built in
step 2. to demonstrate the (mis)behaviour:
VER .\quirk35.exe
Microsoft Windows [Version 6.1.7601]
0x000009FF U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x0000048C U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000463 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000463 ??????????
0x00000461 U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
0x00000461 ??????????
0x00000457 U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
0x00000457 ??????????
0x00000454 U+0ED0 U+0ED1 U+0ED2 U+0ED3 U+0ED4 U+0ED5 U+0ED6 U+0ED7 U+0ED8 U+0ED9
0x00000454 ??????????
0x00000453 U+17E0 U+17E1 U+17E2 U+17E3 U+17E4 U+17E5 U+17E6 U+17E7 U+17E8 U+17E9
0x00000453 ??????????
0x0000044F U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
0x0000044F ??????????
0x0000044E U+0966 U+0967 U+0968 U+0969 U+096A U+096B U+096C U+096D U+096E U+096F
0x0000044E ??????????
0x0000044D U+09E6 U+09E7 U+09E8 U+09E9 U+09EA U+09EB U+09EC U+09ED U+09EE U+09EF
0x0000044D ??????????
0x0000044C U+0D66 U+0D67 U+0D68 U+0D69 U+0D6A U+0D6B U+0D6C U+0D6D U+0D6E U+0D6F
0x0000044C ??????????
0x0000044B U+0CE6 U+0CE7 U+0CE8 U+0CE9 U+0CEA U+0CEB U+0CEC U+0CED U+0CEE U+0CEF
0x0000044B ??????????
0x0000044A U+0C66 U+0C67 U+0C68 U+0C69 U+0C6A U+0C6B U+0C6C U+0C6D U+0C6E U+0C6F
0x0000044A ??????????
0x00000449 U+0BE6 U+0BE7 U+0BE8 U+0BE9 U+0BEA U+0BEB U+0BEC U+0BED U+0BEE U+0BEF
0x00000449 ??????????
0x00000448 U+0B66 U+0B67 U+0B68 U+0B69 U+0B6A U+0B6B U+0B6C U+0B6D U+0B6E U+0B6F
0x00000448 ??????????
0x00000447 U+0AE6 U+0AE7 U+0AE8 U+0AE9 U+0AEA U+0AEB U+0AEC U+0AED U+0AEE U+0AEF
0x00000447 ??????????
0x00000446 U+0A66 U+0A67 U+0A68 U+0A69 U+0A6A U+0A6B U+0A6C U+0A6D U+0A6E U+0A6F
0x00000446 ??????????
0x00000845 U+09E6 U+09E7 U+09E8 U+09E9 U+09EA U+09EB U+09EC U+09ED U+09EE U+09EF
0x00000845 ??????????
0x00000445 U+09E6 U+09E7 U+09E8 U+09E9 U+09EA U+09EB U+09EC U+09ED U+09EE U+09EF
0x00000445 ??????????
0x00000429 U+06F0 U+06F1 U+06F2 U+06F3 U+06F4 U+06F5 U+06F6 U+06F7 U+06F8 U+06F9
0x00000429 ??????????
0x00000420 U+06F0 U+06F1 U+06F2 U+06F3 U+06F4 U+06F5 U+06F6 U+06F7 U+06F8 U+06F9
0x00000420 ??????????
0x0000041E U+0E50 U+0E51 U+0E52 U+0E53 U+0E54 U+0E55 U+0E56 U+0E57 U+0E58 U+0E59
0x0000041E ðñòóôõö÷øù
0x00004001 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00003C01 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00003801 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00003401 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00003001 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00002C01 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00002801 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00002401 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00002001 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000C01 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000801 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
0x00000401 U+0660 U+0661 U+0662 U+0663 U+0664 U+0665 U+0666 U+0667 U+0668 U+0669
NLSWeb
NLSWeb
NLSWeb
OUCH: instead of digits the
GetLocaleInfoA()
function yields the characters '\xF0'
to '\xF9'
for
LOCALE_SNATIVEDIGITS
with the
Locale Identifier
0x0000041E
alias
MAKELCID(MAKELANGID(LANG_THAI, SUBLANG_THAI_THAILAND), SORT_DEFAULT)
!
Ouch: while the
GetLocaleInfoW()
function yields the Arabic numerals
٠١٢٣٤٥٦٧٨٩
alias U+0660
,
U+0661
,
U+0662
,
U+0663
,
U+0664
,
U+0665
,
U+0666
,
U+0667
,
U+0668
and
U+0669
for
LOCALE_SNATIVEDIGITS
with most Arabic
Locale Identifiers
0x000???01
alias
MAKELCID(MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_*), SORT_*)
as well as 0x00000463
alias
MAKELCID(MAKELANGID(LANG_PASHTO, SUBLANG_PASHTO_AFGHANISTAN), SORT_*)
,
0x0000048C
alias
MAKELCID(MAKELANGID(LANG_DARI, SUBLANG_DARI_AFGHANISTAN), SORT_*)
and the
Pseudo-Locale
0x000009FF
, the
GetLocaleInfoA()
function but yields the
ANSI
ASCII
digits 0123456789
!
Oops: neither the simplified Chinese numerals
〇一二三四五六七八九
nor the traditional Chinese numerals
零一二三四五六七八九
are returned for LOCALE_SNATIVEDIGITS
with the Chinese
Locale Identifiers
0x000???04
alias
MAKELCID(MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_*), SORT_*)
.
Note: the Extended Arabic-Indic numerals ۰۱۲۳۴۵۶۷۸۹, the Bengali numerals ০১২৩৪৫৬৭৮৯, the Devanagari numerals ०१२३४५६७८९, the Guarati numerals ૦૧૨૩૪૫૬૭૮૯, the Gurmukhi numerals ੦੧੨੩੪੫੬੭੮੯, the Kannada numerals ೦೧೨೩೪೫೬೭೮೯, the Khmer numerals ០១២៣៤៥៦៧៨៩, the Lao numerals ໐໑໒໓໔໕໖໗໘໙, the Malayalam numerals ൦൧൨൩൪൫൬൭൮൯, the Oriya numerals ୦୧୨୩୪୫୬୭୮୯, the Tamil numerals ௦௧௨௩௪௫௬௭௮௯, the Telugu numerals ౦౧౨౩౪౫౬౭౮౯ and the Thai numerals ๐๑๒๓๔๕๖๗๘๙ are supported, but neither the Tibetan numerals ༠༡༢༣༤༥༦༧༨༩ nor the Mongolian numerals ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙.
Note: the output of substitution characters
?
is expected normal behaviour!
Create the text file quirk35.xml
with the following
content next to the console application quirk35.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk35' processorArchitecture='*' type='win32' version='0.8.1.5' />
<application xmlns='urn:schemas-microsoft-com:asm.v3'>
<windowsSettings>
<activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
</windowsSettings>
</application>
<compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
<application>
<supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
</application>
</compatibility>
<description>Quirk35 Console Application</description>
</assembly>
Note: the double use of an
XML element
named application
is (at least) clumsy and error-prone!
Embed the
Application Manifest
quirk35.xml
created in step 4. in the console
application quirk35.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk35.xml /OUTPUTRESOURCE:quirk35.exeNote: the Manifest Tool
MT.exe
is shipped with the Windows Software Development Kit.
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application quirk35.exe
modified in
step 5. to demonstrate the (mis)behaviour:
VER .\quirk35.exe
Microsoft Windows [Version 10.0.22621.1105] Character count 21 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000463 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000861 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000461 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000860 exceeds 11! Character count 21 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000460 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000457 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000455 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000454 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000453 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000C51 exceeds 11! Character count 103 returned for LOCALE_SNATIVECOUNTRYNAME of LCID 0x00000451 exceeds 80! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000451 exceeds 11! Character count 98 returned for LOCALE_SNATIVECOUNTRYNAME of LCID 0x00007C50 exceeds 80! Character count 98 returned for LOCALE_SNATIVECOUNTRYNAME of LCID 0x00000850 exceeds 80! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044F exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044E exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044D exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044C exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044B exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x0000044A exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000849 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000449 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000448 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000447 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000446 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000845 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000445 exceeds 11! Character count 31 returned for LOCALE_SNATIVEDIGITS of LCID 0x00000439 exceeds 11!OUCH: the character count returned from the
GetLocaleInfoA()
function exceeds the maximum character counts specified in the
documentation for the
LOCALE_SNATIVE*
constants for multiple
Locales!
OOPS: contrary to the explicit statement that
LOCALE_SNATIVEDIGITS
yields the Arabic numerals
٠١٢٣٤٥٦٧٨٩
alias U+0660
,
U+0661
,
U+0662
,
U+0663
,
U+0664
,
U+0665
,
U+0666
,
U+0667
,
U+0668
and
U+0669
with Arabic
Locale Identifiers
0x000???01
, for example 0x00000401
alias
MAKELCID(MAKELANGID(LANG_ARABIC, SUBLANG_ARABIC_SAUDI_ARABIA), SORT_DEFAULT)
,
the
GetLocaleInfoA()
function does not return their character count 21 – it but
yields the
ANSI
ASCII
digits 0123456789
instead!
LCType
parameter which yield native
character strings, in particular
LOCALE_S1159
,
LOCALE_S2359
,
LOCALE_SABBREVDAYNAME*
,
LOCALE_SABBREVMONTHNAME*
,
LOCALE_SCURRENCY
,
LOCALE_SDAYNAME*
,
LOCALE_SLONGDATE
and
LOCALE_SMONTHNAME*
,
is left as an exercise to the reader.
Note: an exploration of the (mis)behaviour when the
system’s (global)
ANSI
and OEM
code pages are set to 65001 alias CP_UTF8
is also left
as an exercise to the reader.
Note: a repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.
Aform of the
GetLocaleInfo()
function Wform, i.e. after conversion from ANSI to Unicode (or vice versa) using the (
legacy) Code Page of the respective Locales and Languages Locale, the character strings should compare as equal.
Aform
Wform!
Create the text file quirk36.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define ERROR_INVALID_LOCALE ERROR_INVALID_PARAMETER
#define LANG_MAX 0x01FF
#define SUBLANG_MAX 0x3F
#ifndef _WIN64
#pragma intrinsic(memcmp)
#else
#pragma function(memcmp)
int memcmp(char const *left, char const *right, size_t count)
{
size_t index;
int delta;
for (index = 0; index < count; index++)
if (delta = left[index] - right[index])
return delta;
return 0;
}
#endif
int wmemcmp(wchar_t const *left, wchar_t const *right, size_t count)
{
size_t index;
int delta;
for (index = 0; index < count; index++)
if (delta = left[index] - right[index])
return delta;
return 0;
}
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
DWORD dwPrimary;
DWORD dwSubLang;
LCID lcLocale;
UINT uiCodePage;
CHAR szANSI[321];
UINT uiANSI;
WCHAR szWide[123];
UINT uiWide;
CHAR szMulti[321];
UINT uiMulti;
WCHAR szUnicode[123];
UINT uiUnicode;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if ((GetACP() != CP_UTF8)
|| (GetOEMCP() != CP_UTF8))
for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
{
lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
if (GetLocaleInfoA(lcLocale,
LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
(LPSTR) &uiCodePage,
sizeof(uiCodePage)) != sizeof(uiCodePage))
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_IDEFAULTANSICODEPAGE of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
{
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVEDIGITS,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVEDIGITS,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(uiCodePage,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(uiCodePage,
MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX DIGITS\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » ANSI = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tANSI » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVECTRYNAME,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVECTRYNAME,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(uiCodePage,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(uiCodePage,
MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX COUNTRY\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » ANSI = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tANSI » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVELANGNAME,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVELANGNAME,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(uiCodePage,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(uiCodePage,
MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX LANGUAGE\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » ANSI = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tANSI » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
}
}
else
for (dwPrimary = LANG_MAX; dwPrimary > LANG_NEUTRAL; dwPrimary--)
for (dwSubLang = SUBLANG_MAX; dwSubLang > SUBLANG_NEUTRAL; dwSubLang--)
{
lcLocale = MAKELCID(MAKELANGID(dwPrimary, dwSubLang), SORT_DEFAULT);
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVEDIGITS,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
{
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVEDIGITS,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(CP_UTF8,
MB_ERR_INVALID_CHARS,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVEDIGITS of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX DIGITS\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » UTF8 = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tUTF8 » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVECTRYNAME,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
{
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVECTRYNAME,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(CP_UTF8,
MB_ERR_INVALID_CHARS,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVECTRYNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX COUNTRY\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » UTF8 = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tUTF8 » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
uiWide = GetLocaleInfoW(lcLocale,
LOCALE_SNATIVELANGNAME,
szWide,
sizeof(szWide) / sizeof(*szWide));
if (uiWide == 0)
{
dwError = GetLastError();
if (dwError != ERROR_INVALID_LOCALE)
PrintConsole(hConsole,
L"GetLocaleInfoW() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError, lcLocale);
}
else
{
uiANSI = GetLocaleInfoA(lcLocale,
LOCALE_SNATIVELANGNAME,
szANSI,
sizeof(szANSI));
if (uiANSI == 0)
PrintConsole(hConsole,
L"GetLocaleInfoA() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
{
uiMulti = WideCharToMultiByte(CP_UTF8,
WC_ERR_INVALID_CHARS,
szWide,
uiWide,
szMulti,
sizeof(szMulti),
(LPCCH) NULL,
(LPBOOL) NULL);
if (uiMulti == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
uiUnicode = MultiByteToWideChar(CP_UTF8,
MB_ERR_INVALID_CHARS,
szANSI,
uiANSI,
szUnicode,
sizeof(szUnicode) / sizeof(*szUnicode));
if (uiUnicode == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu for LOCALE_SNATIVELANGNAME of LCID 0x%08lX\n",
dwError = GetLastError(), lcLocale);
else
if ((uiMulti != 0)
&& (memcmp(szMulti, szANSI, uiANSI) != 0)
|| (wmemcmp(szUnicode, szWide, uiWide) != 0))
PrintConsole(hConsole,
L"0x%08lX LANGUAGE\tGetLocaleInfoW = %u:\t%ls\n"
L"\t\t\tUTF16LE » UTF8 = %u:\t%hs\n"
L"\t\t\tGetLocaleInfoA = %u:\t%hs\n"
L"\t\t\tUTF8 » UTF16LE = %u:\t%ls\n",
lcLocale, uiWide, szWide, uiMulti, szMulti, uiANSI, szANSI, uiUnicode, szUnicode);
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Code Pages
Character Sets
Unicode and Character Sets
Unicode and Character Set Functions
Unicode and Character Set Functions
MultiByteToWideChar()
MultiByteToWideChar()
WideCharToMultiByte()
WideCharToMultiByte()
Note: to avoid failures of the
GetLocaleInfoA()
function with Win32 error code 122 alias
ERROR_INSUFFICIENT_BUFFER
,
the size of its buffers are well above the maximum number of
characters specified in the documentation for
LOCALE_SNATIVE*
which has already been proven wrong in the previous
section
Quirk № 35!
Build the console application quirk36.exe
from the
source file quirk36.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk36.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk36.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk36.c quirk36.c(37) : warning C4706: assignment within conditional expression Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk36.exe quirk36.obj kernel32.lib user32.lib
Execute the console application quirk36.exe
built in
step 2. to demonstrate the (mis)behaviour:
VER .\quirk36.exe
Microsoft Windows [Version 6.1.7601] 0x000009FF DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x000009FF COUNTRY GetLocaleInfoW = 22: [Ρšеϋďŏ Мīґґθřėď !!!] UTF16LE » ANSI = 22: [?????? ???????? !!!] GetLocaleInfoA = 22: [?????? ???????? !!!] ANSI » UTF16LE = 22: [?????? ???????? !!!] 0x000009FF LANGUAGE GetLocaleInfoW = 22: [Рѕёůđó Ľαʼnġџåģе !!!] UTF16LE » ANSI = 22: [?????? ???????? !!!] GetLocaleInfoA = 22: [?????? ???????? !!!] ANSI » UTF16LE = 22: [?????? ???????? !!!] 0x000005FE COUNTRY GetLocaleInfoW = 17: [Ρ§зŭďό Α§ΐд !!] UTF16LE » ANSI = 23: [¯x??? ?t !!] GetLocaleInfoA = 23: [¯x??? ?t !!] ANSI » UTF16LE = 17: [Ρ§з??? Α§?д !!] 0x000005FE LANGUAGE GetLocaleInfoW = 22: [Þѕєΰδō Łªиģųāģз !!!] UTF16LE » ANSI = 25: [????Â? ??y????x !!!] GetLocaleInfoA = 25: [T???Â? ?ay????x !!!] ANSI » UTF16LE = 22: [T???δ? ?aи????з !!!] 0x00000501 COUNTRY GetLocaleInfoW = 11: [Рšěüđõ !] UTF16LE » ANSI = 11: [?ìüð? !] GetLocaleInfoA = 11: [?ìüðo !] ANSI » UTF16LE = 11: [?šěüđo !] 0x00000501 LANGUAGE GetLocaleInfoW = 22: [Þšēūďθ Ļдηğμåģέ !!!] UTF16LE » ANSI = 22: [???ï? ???????? !!!] GetLocaleInfoA = 22: [?euï? L??gµag? !!!] ANSI » UTF16LE = 22: [?šeuď? L??gµag? !!!] 0x0000048C DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00000481 LANGUAGE GetLocaleInfoW = 10: Reo Māori UTF16LE » ANSI = 10: Reo M?ori GetLocaleInfoA = 10: Reo Maori ANSI » UTF16LE = 10: Reo Maori 0x00000480 COUNTRY GetLocaleInfoW = 24: جۇڭخۇا خەلق جۇمھۇرىيىتى UTF16LE » ANSI = 24: Ì??Î?Ç Î?áÞ Ì?ãª?ÑìíìÊì GetLocaleInfoA = 24: Ì??Î?Ç Î?áÞ Ì?ãª?ÑìíìÊì ANSI » UTF16LE = 24: ج??خ?ا خ?لق ج?مھ?رىيىتى 0x00000480 LANGUAGE GetLocaleInfoW = 9: ئۇيغۇرچە UTF16LE » ANSI = 9: Æ?íÛ?Ñ? GetLocaleInfoA = 9: Æ?íÛ?Ñ? ANSI » UTF16LE = 9: ئ?يغ?رچ? 0x00000478 COUNTRY GetLocaleInfoW = 8: ꍏꉸꏓꂱꇭꉼꇩ UTF16LE » ANSI = 8: ??????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00000478 LANGUAGE GetLocaleInfoW = 5: ꆈꌠꁱꂷ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000046D LANGUAGE GetLocaleInfoW = 8: Башҡорт UTF16LE » ANSI = 8: Áàø?îðò GetLocaleInfoA = 8: Áàø?îðò ANSI » UTF16LE = 8: Баш?орт 0x00000465 COUNTRY GetLocaleInfoW = 14: ދިވެހި ރާއްޖެ UTF16LE » ANSI = 8: ??? ??? GetLocaleInfoA = 14: ?????? ?????? ANSI » UTF16LE = 14: ?????? ?????? 0x00000465 LANGUAGE GetLocaleInfoW = 11: ދިވެހިބަސް UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000463 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000463 COUNTRY GetLocaleInfoW = 10: افغانستان UTF16LE » ANSI = 10: ????????? GetLocaleInfoA = 10: ????????? ANSI » UTF16LE = 10: ????????? 0x00000463 LANGUAGE GetLocaleInfoW = 5: پښتو UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000461 DIGITS GetLocaleInfoW = 11: ०१२३४५६७८९ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000461 COUNTRY GetLocaleInfoW = 6: नेपाल UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000461 LANGUAGE GetLocaleInfoW = 7: नेपाली UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000045E COUNTRY GetLocaleInfoW = 6: ኢትዮጵያ UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000045E LANGUAGE GetLocaleInfoW = 5: አማርኛ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000785D COUNTRY GetLocaleInfoW = 4: ᑲᓇᑕ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 4: ??? ANSI » UTF16LE = 4: ??? 0x0000785D LANGUAGE GetLocaleInfoW = 7: ᐃᓄᒃᑎᑐᑦ UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000045D COUNTRY GetLocaleInfoW = 4: ᑲᓇᑕ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 4: ??? ANSI » UTF16LE = 4: ??? 0x0000045D LANGUAGE GetLocaleInfoW = 7: ᐃᓄᒃᑎᑐᑦ UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000045B COUNTRY GetLocaleInfoW = 11: ශ්රී ලංකා UTF16LE » ANSI = 9: ??? ???? GetLocaleInfoA = 11: ????? ???? ANSI » UTF16LE = 11: ????? ???? 0x0000045B LANGUAGE GetLocaleInfoW = 5: සිංහ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000045A COUNTRY GetLocaleInfoW = 6: سوريا UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000045A LANGUAGE GetLocaleInfoW = 7: ܣܘܪܝܝܐ UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x00000457 DIGITS GetLocaleInfoW = 11: ०१२३४५६७८९ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000457 COUNTRY GetLocaleInfoW = 5: भारत UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000457 LANGUAGE GetLocaleInfoW = 7: कोंकणी UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x00000454 DIGITS GetLocaleInfoW = 11: ໐໑໒໓໔໕໖໗໘໙ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000454 COUNTRY GetLocaleInfoW = 11: ສ.ປ.ປ. ລາວ UTF16LE » ANSI = 11: ?.?.?. ??? GetLocaleInfoA = 11: ?.?.?. ??? ANSI » UTF16LE = 11: ?.?.?. ??? 0x00000454 LANGUAGE GetLocaleInfoW = 4: ລາວ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 4: ??? ANSI » UTF16LE = 4: ??? 0x00000453 DIGITS GetLocaleInfoW = 11: ០១២៣៤៥៦៧៨៩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000453 COUNTRY GetLocaleInfoW = 8: កម្ពុជា UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00000453 LANGUAGE GetLocaleInfoW = 6: ខ្មែរ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000451 COUNTRY GetLocaleInfoW = 35: ཀྲུང་ཧྭ་མི་དམངས་སྤྱི་མཐུན་རྒྱལ་ཁབ། UTF16LE » ANSI = 25: ???????????????????????? GetLocaleInfoA = 35: ?????????????????????????????????? ANSI » UTF16LE = 35: ?????????????????????????????????? 0x00000451 LANGUAGE GetLocaleInfoW = 8: བོད་ཡིག UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00007C50 COUNTRY GetLocaleInfoW = 36: ᠪᠦᠭᠦᠳᠡ ᠨᠠᠢᠷᠠᠮᠳᠠᠬᠤ ᠳᠤᠮᠳᠠᠳᠤ ᠠᠷᠠᠳ ᠣᠯᠣᠰ UTF16LE » ANSI = 36: ?????? ?????????? ??????? ???? ???? GetLocaleInfoA = 36: ?????? ?????????? ??????? ???? ???? ANSI » UTF16LE = 36: ?????? ?????????? ??????? ???? ???? 0x00007C50 LANGUAGE GetLocaleInfoW = 13: ᠮᠤᠨᠭᠭᠤᠯ ᠬᠡᠯᠡ UTF16LE » ANSI = 13: ??????? ???? GetLocaleInfoA = 13: ??????? ???? ANSI » UTF16LE = 13: ??????? ???? 0x00000850 COUNTRY GetLocaleInfoW = 36: ᠪᠦᠭᠦᠳᠡ ᠨᠠᠢᠷᠠᠮᠳᠠᠬᠤ ᠳᠤᠮᠳᠠᠳᠤ ᠠᠷᠠᠳ ᠣᠯᠣᠰ UTF16LE » ANSI = 36: ?????? ?????????? ??????? ???? ???? GetLocaleInfoA = 36: ?????? ?????????? ??????? ???? ???? ANSI » UTF16LE = 36: ?????? ?????????? ??????? ???? ???? 0x00000850 LANGUAGE GetLocaleInfoW = 13: ᠮᠤᠨᠭᠭᠤᠯ ᠬᠡᠯᠡ UTF16LE » ANSI = 13: ??????? ???? GetLocaleInfoA = 13: ??????? ???? ANSI » UTF16LE = 13: ??????? ???? 0x0000044F DIGITS GetLocaleInfoW = 11: ०१२३४५६७८९ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044F COUNTRY GetLocaleInfoW = 7: भारतम् UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000044F LANGUAGE GetLocaleInfoW = 8: संस्कृत UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x0000044E DIGITS GetLocaleInfoW = 11: ०१२३४५६७८९ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044E COUNTRY GetLocaleInfoW = 5: भारत UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000044E LANGUAGE GetLocaleInfoW = 6: मराठी UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000044D DIGITS GetLocaleInfoW = 11: ০১২৩৪৫৬৭৮৯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044D COUNTRY GetLocaleInfoW = 5: ভাৰত UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000044D LANGUAGE GetLocaleInfoW = 7: অসমীয়া UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000044C DIGITS GetLocaleInfoW = 11: ൦൧൨൩൪൫൬൭൮൯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044C COUNTRY GetLocaleInfoW = 6: ഭാരതം UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000044C LANGUAGE GetLocaleInfoW = 7: മലയാളം UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x0000044B DIGITS GetLocaleInfoW = 11: ೦೧೨೩೪೫೬೭೮೯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044B COUNTRY GetLocaleInfoW = 5: ಭಾರತ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x0000044B LANGUAGE GetLocaleInfoW = 6: ಕನ್ನಡ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000044A DIGITS GetLocaleInfoW = 11: ౦౧౨౩౪౫౬౭౮౯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x0000044A COUNTRY GetLocaleInfoW = 10: భారత దేశం UTF16LE » ANSI = 8: ??? ??? GetLocaleInfoA = 10: ???? ???? ANSI » UTF16LE = 10: ???? ???? 0x0000044A LANGUAGE GetLocaleInfoW = 7: తెలుగు UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x00000449 DIGITS GetLocaleInfoW = 11: ௦௧௨௩௪௫௬௭௮௯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000449 COUNTRY GetLocaleInfoW = 8: இந்தியா UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00000449 LANGUAGE GetLocaleInfoW = 6: தமிழ் UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000448 DIGITS GetLocaleInfoW = 11: ୦୧୨୩୪୫୬୭୮୯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000448 COUNTRY GetLocaleInfoW = 5: ଭାରତ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000448 LANGUAGE GetLocaleInfoW = 5: ଓଡ଼ିଆ UTF16LE » ANSI = 4: ??? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000447 DIGITS GetLocaleInfoW = 11: ૦૧૨૩૪૫૬૭૮૯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000447 COUNTRY GetLocaleInfoW = 5: ભારત UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000447 LANGUAGE GetLocaleInfoW = 8: ગુજરાતી UTF16LE » ANSI = 7: ?????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00000446 DIGITS GetLocaleInfoW = 11: ੦੧੨੩੪੫੬੭੮੯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000446 COUNTRY GetLocaleInfoW = 5: ਭਾਰਤ UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000446 LANGUAGE GetLocaleInfoW = 7: ਪੰਜਾਬੀ UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 7: ?????? ANSI » UTF16LE = 7: ?????? 0x00000845 DIGITS GetLocaleInfoW = 11: ০১২৩৪৫৬৭৮৯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000845 COUNTRY GetLocaleInfoW = 9: বাংলাদেশ UTF16LE » ANSI = 9: ???????? GetLocaleInfoA = 9: ???????? ANSI » UTF16LE = 9: ???????? 0x00000845 LANGUAGE GetLocaleInfoW = 6: বাংলা UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000445 DIGITS GetLocaleInfoW = 11: ০১২৩৪৫৬৭৮৯ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000445 COUNTRY GetLocaleInfoW = 5: ভারত UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000445 LANGUAGE GetLocaleInfoW = 6: বাংলা UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000043F COUNTRY GetLocaleInfoW = 10: Қазақстан UTF16LE » ANSI = 10: ????????? GetLocaleInfoA = 10: ????????? ANSI » UTF16LE = 10: ????????? 0x0000043F LANGUAGE GetLocaleInfoW = 6: Қазақ UTF16LE » ANSI = 6: ????? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x0000743B LANGUAGE GetLocaleInfoW = 11: sääm´ǩiõll UTF16LE » ANSI = 11: sääm´?iõll GetLocaleInfoA = 11: sääm´kiõll ANSI » UTF16LE = 11: sääm´kiõll 0x0000203B LANGUAGE GetLocaleInfoW = 11: sääm´ǩiõll UTF16LE » ANSI = 11: sääm´?iõll GetLocaleInfoA = 11: sääm´kiõll ANSI » UTF16LE = 11: sääm´kiõll 0x0000083B COUNTRY GetLocaleInfoW = 7: Ruoŧŧa UTF16LE » ANSI = 7: Ruo??a GetLocaleInfoA = 7: Ruotta ANSI » UTF16LE = 7: Ruotta 0x00000439 COUNTRY GetLocaleInfoW = 5: भारत UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 5: ???? ANSI » UTF16LE = 5: ???? 0x00000439 LANGUAGE GetLocaleInfoW = 6: हिंदी UTF16LE » ANSI = 5: ???? GetLocaleInfoA = 6: ????? ANSI » UTF16LE = 6: ????? 0x00000437 COUNTRY GetLocaleInfoW = 11: საქართველო UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000437 LANGUAGE GetLocaleInfoW = 8: ქართული UTF16LE » ANSI = 8: ??????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x00007C2E LANGUAGE GetLocaleInfoW = 15: dolnoserbšćina UTF16LE » ANSI = 15: dolnoserb?ina GetLocaleInfoA = 15: dolnoserbcina ANSI » UTF16LE = 15: dolnoserbšcina 0x0000082E LANGUAGE GetLocaleInfoW = 15: dolnoserbšćina UTF16LE » ANSI = 15: dolnoserb?ina GetLocaleInfoA = 15: dolnoserbcina ANSI » UTF16LE = 15: dolnoserbšcina 0x0000042E COUNTRY GetLocaleInfoW = 7: Němska UTF16LE » ANSI = 7: N?mska GetLocaleInfoA = 7: Nemska ANSI » UTF16LE = 7: Nemska 0x0000042E LANGUAGE GetLocaleInfoW = 16: hornjoserbšćina UTF16LE » ANSI = 16: hornjoserb?ina GetLocaleInfoA = 16: hornjoserbcina ANSI » UTF16LE = 16: hornjoserbšcina 0x0000782C COUNTRY GetLocaleInfoW = 11: Azərbaycan UTF16LE » ANSI = 11: Az?rbaycan GetLocaleInfoA = 11: Az?rbaycan ANSI » UTF16LE = 11: Az?rbaycan 0x0000782C LANGUAGE GetLocaleInfoW = 15: Azərbaycanılı UTF16LE » ANSI = 15: Az?rbaycanýlý GetLocaleInfoA = 15: Az?rbaycanýlý ANSI » UTF16LE = 15: Az?rbaycanılı 0x0000742C COUNTRY GetLocaleInfoW = 11: Азәрбајҹан UTF16LE » ANSI = 11: Àç?ðáà¼?àí GetLocaleInfoA = 11: Àç?ðáà¼?àí ANSI » UTF16LE = 11: Аз?рбај?ан 0x0000742C LANGUAGE GetLocaleInfoW = 16: Азәрбајҹан дили UTF16LE » ANSI = 16: Àç?ðáà¼?àí äèëè GetLocaleInfoA = 16: Àç?ðáà¼?àí äèëè ANSI » UTF16LE = 16: Аз?рбај?ан дили 0x0000082C COUNTRY GetLocaleInfoW = 11: Азәрбајҹан UTF16LE » ANSI = 11: Àç?ðáà¼?àí GetLocaleInfoA = 11: Àç?ðáà¼?àí ANSI » UTF16LE = 11: Аз?рбај?ан 0x0000082C LANGUAGE GetLocaleInfoW = 16: Азәрбајҹан дили UTF16LE » ANSI = 16: Àç?ðáà¼?àí äèëè GetLocaleInfoA = 16: Àç?ðáà¼?àí äèëè ANSI » UTF16LE = 16: Аз?рбај?ан дили 0x0000042C COUNTRY GetLocaleInfoW = 11: Azərbaycan UTF16LE » ANSI = 11: Az?rbaycan GetLocaleInfoA = 11: Az?rbaycan ANSI » UTF16LE = 11: Az?rbaycan 0x0000042C LANGUAGE GetLocaleInfoW = 15: Azərbaycanılı UTF16LE » ANSI = 15: Az?rbaycanýlý GetLocaleInfoA = 15: Az?rbaycanýlý ANSI » UTF16LE = 15: Az?rbaycanılı 0x0000042B COUNTRY GetLocaleInfoW = 9: Հայաստան UTF16LE » ANSI = 9: ???????? GetLocaleInfoA = 9: ???????? ANSI » UTF16LE = 9: ???????? 0x0000042B LANGUAGE GetLocaleInfoW = 8: Հայերեն UTF16LE » ANSI = 8: ??????? GetLocaleInfoA = 8: ??????? ANSI » UTF16LE = 8: ??????? 0x0000042A COUNTRY GetLocaleInfoW = 9: Việt Nam UTF16LE » ANSI = 9: Vi?t Nam GetLocaleInfoA = 9: Vi?t Nam ANSI » UTF16LE = 9: Vi?t Nam 0x0000042A LANGUAGE GetLocaleInfoW = 12: Tiếng Việt UTF16LE » ANSI = 11: Ti?ng Vi?t GetLocaleInfoA = 12: Tiêìng Vi?t ANSI » UTF16LE = 12: Tiếng Vi?t 0x00000429 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000429 COUNTRY GetLocaleInfoW = 6: ایران UTF16LE » ANSI = 6: Ç?ÑÇä GetLocaleInfoA = 6: ÇíÑÇä ANSI » UTF16LE = 6: ايران 0x00007C28 COUNTRY GetLocaleInfoW = 11: Тоҷикистон UTF16LE » ANSI = 11: Òî?èêèñòîí GetLocaleInfoA = 11: Òî?èêèñòîí ANSI » UTF16LE = 11: То?икистон 0x00007C28 LANGUAGE GetLocaleInfoW = 7: Тоҷикӣ UTF16LE » ANSI = 7: Òî?èê? GetLocaleInfoA = 7: Òî?èê? ANSI » UTF16LE = 7: То?ик? 0x00000428 COUNTRY GetLocaleInfoW = 11: Тоҷикистон UTF16LE » ANSI = 11: Òî?èêèñòîí GetLocaleInfoA = 11: Òî?èêèñòîí ANSI » UTF16LE = 11: То?икистон 0x00000428 LANGUAGE GetLocaleInfoW = 7: Тоҷикӣ UTF16LE » ANSI = 7: Òî?èê? GetLocaleInfoA = 7: Òî?èê? ANSI » UTF16LE = 7: То?ик? 0x00000420 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: ?????????? ANSI » UTF16LE = 11: ?????????? 0x00000420 LANGUAGE GetLocaleInfoW = 6: اُردو UTF16LE » ANSI = 5: ?ÑÏæ GetLocaleInfoA = 6: ÇõÑÏæ ANSI » UTF16LE = 6: اُردو 0x00004001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00003C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00003801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00003401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00003001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00002C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00002801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00002401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00002001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00000C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00000801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789 0x00000401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » ANSI = 11: ?????????? GetLocaleInfoA = 11: 0123456789 ANSI » UTF16LE = 11: 0123456789Note: here the output of substitution characters
?
is expected normal behaviour!
OOPS: just for the 3 LCType
parameter
values
LOCALE_SNATIVECTRYNAME
,
LOCALE_SNATIVECURRENCY
and
LOCALE_SNATIVELANGNAME
,
the output from both forms of the
GetLocaleInfo()
function differs in 128 cases!
Create the text file quirk36.xml
with the following
content next to the console application quirk36.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk36' processorArchitecture='*' type='win32' version='0.8.1.5' />
<application xmlns='urn:schemas-microsoft-com:asm.v3'>
<windowsSettings>
<activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
</windowsSettings>
</application>
<compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
<application>
<supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
</application>
</compatibility>
<description>Quirk36 Console Application</description>
</assembly>
Note: the double use of an
XML element
named application
is (at least) clumsy and error-prone!
Embed the
Application Manifest
quirk36.xml
created in step 4. in the console
application quirk36.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk36.xml /OUTPUTRESOURCE:quirk36.exeNote: the Manifest Tool
MT.exe
is shipped with the Windows Software Development Kit.
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application quirk36.exe
modified in
step 5. to demonstrate the (mis)behaviour:
VER .\quirk36.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 10.0.22621.1105] 0x000009FF DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 0x000009FF COUNTRY GetLocaleInfoW = 22: [Ρšеϋďŏ Мīґґθřėď !!!] UTF16LE » UTF8 = 36: [Ρšеϋďŏ Мīґґθřėď !!!] GetLocaleInfoA = 22: [?????? ???????? !!!] UTF8 » UTF16LE = 22: [?????? ???????? !!!] 0x000009FF LANGUAGE GetLocaleInfoW = 22: [Рѕёůđó Ľαʼnġџåģе !!!] UTF16LE » UTF8 = 36: [Рѕёůđó Ľαʼnġџåģе !!!] GetLocaleInfoA = 22: [?????? ???????? !!!] UTF8 » UTF16LE = 22: [?????? ???????? !!!] MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x000005FE MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x000005FE MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000901 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000501 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000501 0x00007C92 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C92 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C92 0x00000492 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000492 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000492 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000491 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000491 0x0000048C DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000048C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000048C 0x00007C86 LANGUAGE GetLocaleInfoW = 8: Kʼicheʼ UTF16LE » UTF8 = 10: Kʼicheʼ GetLocaleInfoA = 8: K'iche' UTF8 » UTF16LE = 8: K'iche' 0x00000486 LANGUAGE GetLocaleInfoW = 8: Kʼicheʼ UTF16LE » UTF8 = 10: Kʼicheʼ GetLocaleInfoA = 8: K'iche' UTF8 » UTF16LE = 8: K'iche' MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000485 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000485 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000484 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000484 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000482 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000480 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000480 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000047E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000047C 0x00000475 COUNTRY GetLocaleInfoW = 20: ʻAmelika Hui Pū ʻIa UTF16LE » UTF8 = 23: ʻAmelika Hui Pū ʻIa GetLocaleInfoA = 20: ?Amelika Hui Pu ?Ia UTF8 » UTF16LE = 20: ?Amelika Hui Pu ?Ia 0x00000475 LANGUAGE GetLocaleInfoW = 15: ʻŌlelo Hawaiʻi UTF16LE » UTF8 = 18: ʻŌlelo Hawaiʻi GetLocaleInfoA = 15: ?Olelo Hawai?i UTF8 » UTF16LE = 15: ?Olelo Hawai?i MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000474 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000474 0x00000470 COUNTRY GetLocaleInfoW = 9: Naịjịrịa UTF16LE » UTF8 = 15: Naịjịrịa GetLocaleInfoA = 9: Na?j?r?a UTF8 » UTF16LE = 9: Na?j?r?a MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000046E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000046E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000046D MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000046D MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C6B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000046A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000046A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000466 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000462 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C5F 0x00007C5F LANGUAGE GetLocaleInfoW = 18: Tamaziɣt n laṭlaṣ UTF16LE » UTF8 = 23: Tamaziɣt n laṭlaṣ GetLocaleInfoA = 18: Tamazi?t n la?la? UTF8 » UTF16LE = 18: Tamazi?t n la?la? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000085F 0x0000085F LANGUAGE GetLocaleInfoW = 18: Tamaziɣt n laṭlaṣ UTF16LE » UTF8 = 23: Tamaziɣt n laṭlaṣ GetLocaleInfoA = 18: Tamazi?t n la?la? UTF8 » UTF16LE = 18: Tamazi?t n la?la? 0x0000045F DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000045F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000045F 0x00007C59 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C59 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C59 0x00000859 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000859 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000859 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000456 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007850 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007850 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000450 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000450 0x00007C46 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C46 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C46 0x00000846 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000846 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000846 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000444 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000444 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C43 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C43 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007843 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007843 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000843 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000843 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000443 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000443 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000442 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000442 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000440 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000440 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000083C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C3B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000783B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000743B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000743B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000703B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000703B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000243B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000243B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000203B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000203B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C3B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000183B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000183B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000143B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000103B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C3B 0x0000083B COUNTRY GetLocaleInfoW = 7: Ruoŧŧa UTF16LE » UTF8 = 9: Ruoŧŧa GetLocaleInfoA = 7: Ruotta UTF8 » UTF16LE = 7: Ruotta MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000083B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000043B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000438 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000438 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000042F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000042F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C2E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000082E 0x0000042E COUNTRY GetLocaleInfoW = 7: Němska UTF16LE » UTF8 = 8: Němska GetLocaleInfoA = 7: Nemska UTF8 » UTF16LE = 7: Nemska MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000042E 0x0000782C COUNTRY GetLocaleInfoW = 11: Azərbaycan UTF16LE » UTF8 = 12: Azərbaycan GetLocaleInfoA = 11: Az?rbaycan UTF8 » UTF16LE = 11: Az?rbaycan 0x0000782C LANGUAGE GetLocaleInfoW = 11: azərbaycan UTF16LE » UTF8 = 12: azərbaycan GetLocaleInfoA = 11: az?rbaycan UTF8 » UTF16LE = 11: az?rbaycan MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000742C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000742C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000082C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000082C 0x0000042C COUNTRY GetLocaleInfoW = 11: Azərbaycan UTF16LE » UTF8 = 12: Azərbaycan GetLocaleInfoA = 11: Az?rbaycan UTF8 » UTF16LE = 11: Az?rbaycan 0x0000042C LANGUAGE GetLocaleInfoW = 11: azərbaycan UTF16LE » UTF8 = 12: azərbaycan GetLocaleInfoA = 11: az?rbaycan UTF8 » UTF16LE = 11: az?rbaycan 0x0000042A COUNTRY GetLocaleInfoW = 9: Việt Nam UTF16LE » UTF8 = 11: Việt Nam GetLocaleInfoA = 9: Vi?t Nam UTF8 » UTF16LE = 9: Vi?t Nam 0x0000042A LANGUAGE GetLocaleInfoW = 11: Tiếng Việt UTF16LE » UTF8 = 15: Tiếng Việt GetLocaleInfoA = 11: Ti?ng Vi?t UTF8 » UTF16LE = 11: Ti?ng Vi?t 0x00000429 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000429 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000429 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C28 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C28 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000428 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000428 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000427 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000426 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000424 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000423 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000423 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000422 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000422 0x00000820 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000820 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000820 0x00000420 DIGITS GetLocaleInfoW = 11: ۰۱۲۳۴۵۶۷۸۹ UTF16LE » UTF8 = 21: ۰۱۲۳۴۵۶۷۸۹ GetLocaleInfoA = 11: ?????????? UTF8 » UTF16LE = 11: ?????????? MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000420 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000420 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000041F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000041F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVEDIGITS of LCID 0x0000041E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000041E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000041E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000041C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000041B MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00006C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00006C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000641A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000641A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000301A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000301A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000281A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000281A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000201A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000201A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C1A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000081A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000819 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000819 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000419 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000419 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000818 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000418 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000418 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000816 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000416 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C14 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000414 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000813 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000412 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000412 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000411 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000411 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040F MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040E MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040D MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040D MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000380C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000340C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000300C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000300C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000280C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000280C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000240C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000240C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000200C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000200C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000180C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000140C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000100C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C0C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000080C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040C MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00005C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000580A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000580A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000540A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000500A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00004C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000480A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000440A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000400A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000380A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000340A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000300A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000280A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000280A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000240A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000200A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000180A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000180A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000140A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000100A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C0A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000080A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000080A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x0000040A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x0000040A MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000408 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000408 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C07 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000405 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000405 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007C04 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007C04 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00007804 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00007804 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001404 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001404 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001004 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001004 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C04 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C04 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000804 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000804 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000404 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000404 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000803 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000403 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000402 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000402 0x00004001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00004001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00004001 0x00003C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003C01 0x00003801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003801 0x00003401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003401 0x00003001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00003001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00003001 0x00002C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002C01 0x00002801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002801 0x00002401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002401 0x00002001 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00002001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00002001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00001001 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00001001 0x00000C01 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000C01 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000C01 0x00000801 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000801 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000801 0x00000401 DIGITS GetLocaleInfoW = 11: ٠١٢٣٤٥٦٧٨٩ UTF16LE » UTF8 = 21: ٠١٢٣٤٥٦٧٨٩ GetLocaleInfoA = 11: 0123456789 UTF8 » UTF16LE = 11: 0123456789 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVECTRYNAME of LCID 0x00000401 MultiByteToWideChar() returned error 1113 for LOCALE_SNATIVELANGNAME of LCID 0x00000401 0x459 (WIN32: 1113 ERROR_NO_UNICODE_TRANSLATION) -- 1113 (1113) Error message text: No mapping for the Unicode character exists in the target multi-byte code page. CertUtil: -error command completed successfully.OUCH¹: here the output of substitution characters
?
is unexpected,
abnormal and erroneous behaviour!
OUCH²: in 260 cases the
MultiByteToWideChar()
function fails with Win32 error code 1113 alias
ERROR_NO_UNICODE_TRANSLATION
,
i.e. the
GetLocaleInfoA()
function returns invalid
UTF-8
in these 260 cases – a desastrous result!
OUCH³: in 41 cases the UTF-8 output differs from the UTF-16LE output.
OUCH⁴: in 8 of these 41 cases the
GetLocaleInfoA()
returns a character string only containing the
ASCII
substitution character ?
, i.e.
the
UTF-8
support in Windows’
National Language Support
functions is a very bad joke!
LCType
parameter which yield native
character strings, in particular
LOCALE_S1159
,
LOCALE_S2359
,
LOCALE_SABBREVDAYNAME*
,
LOCALE_SABBREVMONTHNAME*
,
LOCALE_SCURRENCY
,
LOCALE_SDAYNAME*
,
LOCALE_SLONGDATE
and
LOCALE_SMONTHNAME*
,
is left as an exercise to the reader.
Note: a repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.
-A vs. -W APIs:
Until recently, Windows has emphasized "Unicode" -W variants over -A APIs. However, recent releases have used the ANSI code page and -A APIs as a means to introduce UTF-8 support to apps. If the ANSI code page is configured for UTF-8, -A APIs typically operate in UTF-8. This model has the benefit of supporting existing code built with -A APIs without any code changes.The Win32 function
wsprintfA()
is documented in the
MSDN as
follows:
Writes formatted data to the specified buffer. Any arguments are converted and copied to the output buffer according to the corresponding format specification in the format string. The function appends a terminating null character to the characters it writes, but the return value does not include the terminating null character in its character count.Oops: this specification but fails to document the types[…]
[…]int wsprintfA( LPSTR lpOut, LPCSTR lpFmt, ... );
[…]
- lpOut [out]
- The buffer that is to receive the formatted output. The maximum size of the buffer is 1,024 bytes.
- lpFmt [in]
- The format-control specifications. In addition to ordinary ASCII characters, a format specification for each argument appears in this string. For more information about the format specification, see the Remarks section.
- ... [in]
- One or more optional arguments. The number and type of argument parameters depend on the corresponding format-control specifications in the lpFmt parameter.
If the function succeeds, the return value is the number of characters stored in the output buffer, not counting the terminating null character.
If the function fails, the return value is less than the length of the expected output. To get extended error information, call GetLastError.
[…]
A format specification has the following form:
%[-][#][0][width][.precision]type
Each field is a single character or a number signifying a particular format option. The type characters that appear after the last optional format field determine whether the associated argument is interpreted as a character, a string, or a number. The simplest format specification contains only the percent sign and a type character (for example, %s). The optional fields control other aspects of the formatting. Following are the optional and required fields and their meanings.
Field Meaning … … type Output the corresponding argument as a character, a string, or a number. This field can be any of the following values.
- […]
- ls, lS
- String. This value is always interpreted as type LPWSTR, even when the calling application does not define Unicode. This value is equivalent to ws.
lp
alias tp
alias wp
alias Ip
(all equivalent to p
),
tx
(equivalent to Ix
) and tX
(equivalent to IX
) for unsigned pointers in hexadecimal
notation, the types td
alias Id
alias
ti
alias Ii
for signed pointers in decimal
notation, the types tu
alias Iu
for
unsigned pointers in decimal notation, the types I32d
(equivalent to d
and ld
) alias
I32i
(equivalent to i
and li
)
for signed 32-bit integers in decimal notation, the type
I32u
(equivalent to u
and lu
)
for unsigned 32-bit integers in decimal notation, the types
I32x
(equivalent to x
and lx
)
and I32X
(equivalent to X
and
lX
) for 32-bit integers in hexadecimal notation, the
types I64d
alias I64i
for signed 64-bit
integers in decimal notation, the type I64u
for
unsigned 64-bit integers in decimal notation, the types
I64x
and I64X
for 64-bit integers in
hexadecimal notation, the types wc
alias
wC
(both equivalent to lc
and
lC
) for widecharacters, the type
wS
(equivalent to ws
, ls
and
lS
) for widecharacter strings, the types
wd
(equivalent to d
and ld
)
alias wi
(equivalent to i
and
li
) for signed 32-bit integers in decimal notation, the
type wu
(equivalent to u
and
lu
) for unsigned 32-bit integers in decimal notation,
plus the types wx
(equivalent to x
and
lx
) and wX
(equivalent to X
and lX
) for 32-bit integers in hexadecimal notation!
Size Specification
Strings
x64: Starting Out in 64-Bit Windows Systems with Visual C++
Create the text file quirk37.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define memset __stosb
#define wmemset __stosw
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WCHAR szWide[1024];
CHAR szANSI[1024];
CHAR szQuirk[1024];
UINT uiQuirk;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
wmemset(szWide, L'€', 1023);
szWide[1023] = L'\0';
memset(szANSI, '€', 1023);
szANSI[1023] = '\0';
SetLastError(123456789);
uiQuirk = wsprintfA(szQuirk, "%ls", szWide);
if (uiQuirk != 1023)
PrintConsole(hConsole,
L"wsprintfA() returned %u\n"
L"GetLastError() returned %lu\n",
uiQuirk, dwError = GetLastError());
else if (strcmp(szQuirk, szANSI) == 0)
PrintConsole(hConsole,
L"wsprintfA() returned a string of %u \'%hc\' characters\n",
uiQuirk, *szQuirk);
else
PrintConsole(hConsole,
L"wsprintfA() returned a differing string \'%hs\' of %u characters\n",
szQuirk, uiQuirk);
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk37.exe
from the
source file quirk37.c
created in step 1.:
SET CL=/GAF /Gs8192 /Gy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk37.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk37.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk37.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk37.exe quirk37.obj kernel32.lib user32.lib
Execute the console application quirk37.exe
built in
step 2. to demonstrate the legacy behaviour:
.\quirk37.exe
wsprintfA() returned the correct string of 1023 '€' characters
Create the text file quirk37.xml
with the following
content next to the console application quirk37.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk37' processorArchitecture='*' type='win32' version='0.8.1.5' />
<application xmlns='urn:schemas-microsoft-com:asm.v3'>
<windowsSettings>
<activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
</windowsSettings>
</application>
<compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
<application>
<supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
</application>
</compatibility>
<description>Quirk37 Console Application</description>
</assembly>
Note: the double use of an
XML element
named application
is (at least) clumsy and error-prone!
Embed the
Application Manifest
quirk37.xml
created in step 4. in the console
application quirk37.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk37.xml /OUTPUTRESOURCE:quirk37.exeNote: the Manifest Tool
MT.exe
is shipped with the Windows Software Development Kit.
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application quirk37.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the (mis)behaviour:
VER .\quirk37.exe
Microsoft Windows [Version 10.0.22621.1105]
wsprintfA() returned 0
GetLastError() returned 123456789
OUCH¹: contrary to the first highlighted
statement of the documentation cited above, existing code but
fails when the
ANSI
code page is configured for
UTF-8!
OUCH²: contrary to the last highlighted
statement of the documentation cited above, the
wsprintfA()
function fails to set the Win32 error code!
wsprintfA()
is documented in the
MSDN as
follows:
Writes formatted data to the specified buffer. Any arguments are converted and copied to the output buffer according to the corresponding format specification in the format string. The function appends a terminating null character to the characters it writes, but the return value does not include the terminating null character in its character count.[…]
[…]int wsprintfA( LPSTR lpOut, LPCSTR lpFmt, ... );
If the function succeeds, the return value is the number of characters stored in the output buffer, not counting the terminating null character.
[…]
A format specification has the following form:
%[-][#][0][width][.precision]type
Each field is a single character or a number signifying a particular format option. The type characters that appear after the last optional format field determine whether the associated argument is interpreted as a character, a string, or a number. The simplest format specification contains only the percent sign and a type character (for example, %s). The optional fields control other aspects of the formatting. Following are the optional and required fields and their meanings.
Field Meaning … … .precision […] For strings, copy the specified maximum number of characters to the output buffer.
Create the text file quirk38.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define CP1252 L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
DWORD dwWide;
CHAR szANSI[4];
UINT uiANSI;
CHAR szQuirk[4];
UINT uiQuirk;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CP1252, sizeof(CP1252) / sizeof(*CP1252),
(LPSTR) NULL, 0,
(LPCCH) NULL, (LPBOOL) NULL) == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu\n",
dwError = GetLastError());
else
for (dwWide = 0; dwWide < sizeof(CP1252) / sizeof(*CP1252) - 1; dwWide++)
{
uiANSI = WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CP1252 + dwWide, 1,
szANSI, sizeof(szANSI) - 1,
(LPCCH) NULL, (LPBOOL) NULL);
if (uiANSI == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for wide character U+%04hX\n",
dwError = GetLastError(), CP1252[dwWide]);
else
{
szANSI[uiANSI++] = '\0';
#if 0
uiQuirk = wsprintfA(szQuirk, "%lc", CP1252[dwWide]);
#else
uiQuirk = wsprintfA(szQuirk, "%.1ls", CP1252 + dwWide);
#endif
if (uiQuirk == 0)
PrintConsole(hConsole,
L"wsprintfA() returned error %lu for wide character \'%lc\' (U+%04hX)\n",
dwError = GetLastError(), CP1252[dwWide], CP1252[dwWide]);
else
{
if (uiQuirk > 1)
PrintConsole(hConsole,
L"wsprintfA() returned string \'%hs\' of %u characters for wide character \'%lc\' (U+%04hX)\n",
szQuirk, uiQuirk, CP1252[dwWide], CP1252[dwWide]);
if (memcmp(szQuirk, szANSI, uiANSI) != 0)
PrintConsole(hConsole,
L"wsprintfA() returned DIFFERENT string \'%hs\' of %u characters for wide character \'%lc\' (U+%04hX)\n",
szQuirk, uiQuirk, CP1252[dwWide], CP1252[dwWide]);
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk38.exe
from the
source file quirk38.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk38.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk38.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk38.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk38.exe quirk38.obj kernel32.lib user32.lib
Execute the console application quirk38.exe
built in
step 2. to demonstrate the legacy behaviour:
.\quirk38.exe
Create the text file quirk38.xml
with the following
content next to the console application quirk38.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk38' processorArchitecture='*' type='win32' version='0.8.1.5' />
<application xmlns='urn:schemas-microsoft-com:asm.v3'>
<windowsSettings>
<activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
</windowsSettings>
</application>
<compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
<application>
<supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
</application>
</compatibility>
<description>Quirk38 Console Application</description>
</assembly>
Note: the double use of an
XML element
named application
is (at least) clumsy and error-prone!
Embed the
Application Manifest
quirk38.xml
created in step 4. in the console
application quirk38.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk38.xml /OUTPUTRESOURCE:quirk38.exeNote: the Manifest Tool
MT.exe
is shipped with the Windows Software Development Kit.
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application quirk38.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the (mis)behaviour and prove the documentation cited
above wrong:
VER .\quirk38.exe
Microsoft Windows [Version 10.0.22621.1105] wsprintfA() returned string '€' of 3 characters for wide character '€' (U+20AC) wsprintfA() returned string '‚' of 3 characters for wide character '‚' (U+201A) wsprintfA() returned string 'ƒ' of 2 characters for wide character 'ƒ' (U+0192) wsprintfA() returned string '„' of 3 characters for wide character '„' (U+201E) wsprintfA() returned string '…' of 3 characters for wide character '…' (U+2026) wsprintfA() returned string '†' of 3 characters for wide character '†' (U+2020) wsprintfA() returned string '‡' of 3 characters for wide character '‡' (U+2021) wsprintfA() returned string 'ˆ' of 2 characters for wide character 'ˆ' (U+02C6) wsprintfA() returned string '‰' of 3 characters for wide character '‰' (U+2030) wsprintfA() returned string 'Š' of 2 characters for wide character 'Š' (U+0160) wsprintfA() returned string '‹' of 3 characters for wide character '‹' (U+2039) wsprintfA() returned string 'Œ' of 2 characters for wide character 'Œ' (U+0152) wsprintfA() returned string 'Ž' of 2 characters for wide character 'Ž' (U+017D) wsprintfA() returned string '‘' of 3 characters for wide character '‘' (U+2018) wsprintfA() returned string '’' of 3 characters for wide character '’' (U+2019) wsprintfA() returned string '“' of 3 characters for wide character '“' (U+201C) wsprintfA() returned string '”' of 3 characters for wide character '”' (U+201D) wsprintfA() returned string '•' of 3 characters for wide character '•' (U+2022) wsprintfA() returned string '–' of 3 characters for wide character '–' (U+2013) wsprintfA() returned string '—' of 3 characters for wide character '—' (U+2014) wsprintfA() returned string '˜' of 2 characters for wide character '˜' (U+02DC) wsprintfA() returned string '™' of 3 characters for wide character '™' (U+2122) wsprintfA() returned string 'š' of 2 characters for wide character 'š' (U+0161) wsprintfA() returned string '›' of 3 characters for wide character '›' (U+203A) wsprintfA() returned string 'œ' of 2 characters for wide character 'œ' (U+0153) wsprintfA() returned string 'ž' of 2 characters for wide character 'ž' (U+017E) wsprintfA() returned string 'Ÿ' of 2 characters for wide character 'Ÿ' (U+0178) wsprintfA() returned string ' ' of 2 characters for wide character ' ' (U+00A0) wsprintfA() returned string '¡' of 2 characters for wide character '¡' (U+00A1) wsprintfA() returned string '¢' of 2 characters for wide character '¢' (U+00A2) wsprintfA() returned string '£' of 2 characters for wide character '£' (U+00A3) wsprintfA() returned string '¤' of 2 characters for wide character '¤' (U+00A4) wsprintfA() returned string '¥' of 2 characters for wide character '¥' (U+00A5) wsprintfA() returned string '¦' of 2 characters for wide character '¦' (U+00A6) wsprintfA() returned string '§' of 2 characters for wide character '§' (U+00A7) wsprintfA() returned string '¨' of 2 characters for wide character '¨' (U+00A8) wsprintfA() returned string '©' of 2 characters for wide character '©' (U+00A9) wsprintfA() returned string 'ª' of 2 characters for wide character 'ª' (U+00AA) wsprintfA() returned string '«' of 2 characters for wide character '«' (U+00AB) wsprintfA() returned string '¬' of 2 characters for wide character '¬' (U+00AC) wsprintfA() returned string '' of 2 characters for wide character '' (U+00AD) wsprintfA() returned string '®' of 2 characters for wide character '®' (U+00AE) wsprintfA() returned string '¯' of 2 characters for wide character '¯' (U+00AF) wsprintfA() returned string '°' of 2 characters for wide character '°' (U+00B0) wsprintfA() returned string '±' of 2 characters for wide character '±' (U+00B1) wsprintfA() returned string '²' of 2 characters for wide character '²' (U+00B2) wsprintfA() returned string '³' of 2 characters for wide character '³' (U+00B3) wsprintfA() returned string '´' of 2 characters for wide character '´' (U+00B4) wsprintfA() returned string 'µ' of 2 characters for wide character 'µ' (U+00B5) wsprintfA() returned string '¶' of 2 characters for wide character '¶' (U+00B6) wsprintfA() returned string '·' of 2 characters for wide character '·' (U+00B7) wsprintfA() returned string '¸' of 2 characters for wide character '¸' (U+00B8) wsprintfA() returned string '¹' of 2 characters for wide character '¹' (U+00B9) wsprintfA() returned string 'º' of 2 characters for wide character 'º' (U+00BA) wsprintfA() returned string '»' of 2 characters for wide character '»' (U+00BB) wsprintfA() returned string '¼' of 2 characters for wide character '¼' (U+00BC) wsprintfA() returned string '½' of 2 characters for wide character '½' (U+00BD) wsprintfA() returned string '¾' of 2 characters for wide character '¾' (U+00BE) wsprintfA() returned string '¿' of 2 characters for wide character '¿' (U+00BF) wsprintfA() returned string 'À' of 2 characters for wide character 'À' (U+00C0) wsprintfA() returned string 'Á' of 2 characters for wide character 'Á' (U+00C1) wsprintfA() returned string 'Â' of 2 characters for wide character 'Â' (U+00C2) wsprintfA() returned string 'Ã' of 2 characters for wide character 'Ã' (U+00C3) wsprintfA() returned string 'Ä' of 2 characters for wide character 'Ä' (U+00C4) wsprintfA() returned string 'Å' of 2 characters for wide character 'Å' (U+00C5) wsprintfA() returned string 'Æ' of 2 characters for wide character 'Æ' (U+00C6) wsprintfA() returned string 'Ç' of 2 characters for wide character 'Ç' (U+00C7) wsprintfA() returned string 'È' of 2 characters for wide character 'È' (U+00C8) wsprintfA() returned string 'É' of 2 characters for wide character 'É' (U+00C9) wsprintfA() returned string 'Ê' of 2 characters for wide character 'Ê' (U+00CA) wsprintfA() returned string 'Ë' of 2 characters for wide character 'Ë' (U+00CB) wsprintfA() returned string 'Ì' of 2 characters for wide character 'Ì' (U+00CC) wsprintfA() returned string 'Í' of 2 characters for wide character 'Í' (U+00CD) wsprintfA() returned string 'Î' of 2 characters for wide character 'Î' (U+00CE) wsprintfA() returned string 'Ï' of 2 characters for wide character 'Ï' (U+00CF) wsprintfA() returned string 'Ð' of 2 characters for wide character 'Ð' (U+00D0) wsprintfA() returned string 'Ñ' of 2 characters for wide character 'Ñ' (U+00D1) wsprintfA() returned string 'Ò' of 2 characters for wide character 'Ò' (U+00D2) wsprintfA() returned string 'Ó' of 2 characters for wide character 'Ó' (U+00D3) wsprintfA() returned string 'Ô' of 2 characters for wide character 'Ô' (U+00D4) wsprintfA() returned string 'Õ' of 2 characters for wide character 'Õ' (U+00D5) wsprintfA() returned string 'Ö' of 2 characters for wide character 'Ö' (U+00D6) wsprintfA() returned string '×' of 2 characters for wide character '×' (U+00D7) wsprintfA() returned string 'Ø' of 2 characters for wide character 'Ø' (U+00D8) wsprintfA() returned string 'Ù' of 2 characters for wide character 'Ù' (U+00D9) wsprintfA() returned string 'Ú' of 2 characters for wide character 'Ú' (U+00DA) wsprintfA() returned string 'Û' of 2 characters for wide character 'Û' (U+00DB) wsprintfA() returned string 'Ü' of 2 characters for wide character 'Ü' (U+00DC) wsprintfA() returned string 'Ý' of 2 characters for wide character 'Ý' (U+00DD) wsprintfA() returned string 'Þ' of 2 characters for wide character 'Þ' (U+00DE) wsprintfA() returned string 'ß' of 2 characters for wide character 'ß' (U+00DF) wsprintfA() returned string 'à' of 2 characters for wide character 'à' (U+00E0) wsprintfA() returned string 'á' of 2 characters for wide character 'á' (U+00E1) wsprintfA() returned string 'â' of 2 characters for wide character 'â' (U+00E2) wsprintfA() returned string 'ã' of 2 characters for wide character 'ã' (U+00E3) wsprintfA() returned string 'ä' of 2 characters for wide character 'ä' (U+00E4) wsprintfA() returned string 'å' of 2 characters for wide character 'å' (U+00E5) wsprintfA() returned string 'æ' of 2 characters for wide character 'æ' (U+00E6) wsprintfA() returned string 'ç' of 2 characters for wide character 'ç' (U+00E7) wsprintfA() returned string 'è' of 2 characters for wide character 'è' (U+00E8) wsprintfA() returned string 'é' of 2 characters for wide character 'é' (U+00E9) wsprintfA() returned string 'ê' of 2 characters for wide character 'ê' (U+00EA) wsprintfA() returned string 'ë' of 2 characters for wide character 'ë' (U+00EB) wsprintfA() returned string 'ì' of 2 characters for wide character 'ì' (U+00EC) wsprintfA() returned string 'í' of 2 characters for wide character 'í' (U+00ED) wsprintfA() returned string 'î' of 2 characters for wide character 'î' (U+00EE) wsprintfA() returned string 'ï' of 2 characters for wide character 'ï' (U+00EF) wsprintfA() returned string 'ð' of 2 characters for wide character 'ð' (U+00F0) wsprintfA() returned string 'ñ' of 2 characters for wide character 'ñ' (U+00F1) wsprintfA() returned string 'ò' of 2 characters for wide character 'ò' (U+00F2) wsprintfA() returned string 'ó' of 2 characters for wide character 'ó' (U+00F3) wsprintfA() returned string 'ô' of 2 characters for wide character 'ô' (U+00F4) wsprintfA() returned string 'õ' of 2 characters for wide character 'õ' (U+00F5) wsprintfA() returned string 'ö' of 2 characters for wide character 'ö' (U+00F6) wsprintfA() returned string '÷' of 2 characters for wide character '÷' (U+00F7) wsprintfA() returned string 'ø' of 2 characters for wide character 'ø' (U+00F8) wsprintfA() returned string 'ù' of 2 characters for wide character 'ù' (U+00F9) wsprintfA() returned string 'ú' of 2 characters for wide character 'ú' (U+00FA) wsprintfA() returned string 'û' of 2 characters for wide character 'û' (U+00FB) wsprintfA() returned string 'ü' of 2 characters for wide character 'ü' (U+00FC) wsprintfA() returned string 'ý' of 2 characters for wide character 'ý' (U+00FD) wsprintfA() returned string 'þ' of 2 characters for wide character 'þ' (U+00FE) wsprintfA() returned string 'ÿ' of 2 characters for wide character 'ÿ' (U+00FF)OOPS: for each of the 123 wide characters from the string
CP1252
, the
wsprintfA()
function returns a character count greater 1, i.e. the documentation
cited above confuses character alias code point and
byte alias code unit!
wnsprintfA()
is documented in the
MSDN as
follows:
Takes a variable-length argument list and returns the values of the arguments as a printf-style formatted string.[…]
[…]int wnsprintfA( LPSTR pszDest, int cchDest, LPCSTR pszFmt, ... );
Returns the number of characters written to the buffer, excluding any terminating NULL characters. A negative value is returned if an error occurs.
[…]
This is a Windows version of sprintf. It does not support floating-point or pointer types. It supports only the left alignment flag.
Takes a list of arguments and returns the values of the arguments as a printf-style formatted string.The MSDN article Format Specification Syntax:[…]
int wvnsprintfA( LPSTR pszDest, int cchDest, LPCSTR pszFmt, va_list arglist );
printf
and wprintf
Functions
specifies:
A conversion specification consists of optional and required fields in this form:Flag Directives printf Width Specification Precision Specification Size Specification printf Type Field Characters%[flags][width][.precision][size]type
[…]
The
type
character determines either the interpretation ofprecision
or the default precision whenprecision
is omitted, as shown in the following table.
Type Meaning Default … … s
,S
The precision specifies the maximum number of characters to be printed. Characters in excess of precision are not printed. Characters are printed until a null character is encountered.
Create the text file quirk39.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shlwapi.h>
#define CP1252 L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
INT niANSI;
CHAR szANSI[57];
DWORD dwWide;
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
niANSI = wnsprintfA(szANSI, sizeof(szANSI),
"%+09d|% i|%#p|%-*.*ws|%#x",
GetACP(), GetOEMCP(), wmainCRTStartup, 7, 5, CP1252, 0xDEADBEEF);
if (niANSI < 0)
PrintConsole(hConsole,
L"wnsprintfA() returned %i\n",
niANSI);
else
PrintConsole(hConsole,
L"wnsprintfA() returned string \'%hs\' of %i characters\n",
szANSI, niANSI);
for (dwWide = 0; dwWide < sizeof(CP1252) / sizeof(*CP1252) - 1; dwWide++)
{
#if 0
niANSI = wnsprintfA(szANSI, sizeof(szANSI), "%lc", CP1252[dwWide]);
#else
niANSI = wnsprintfA(szANSI, sizeof(szANSI), "%.1ls", CP1252 + dwWide);
#endif
if (niANSI < 0)
PrintConsole(hConsole,
L"wnsprintfA() returned %i for wide character \'%lc\' (U+%04hX)\n",
CP1252[dwWide], CP1252[dwWide]);
else if (niANSI != 1)
PrintConsole(hConsole,
L"wnsprintfA() returned string \'%hs\' of %i characters for wide character \'%lc\' (U+%04hX)\n",
szANSI, niANSI, CP1252[dwWide], CP1252[dwWide]);
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk39.exe
from the
source file quirk39.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk39.c kernel32.lib shlwapi.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk39.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk39.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk39.exe quirk39.obj kernel32.lib shlwapi.lib user32.lib
Execute the console application quirk39.exe
built in
step 2. to demonstrate the legacy behaviour:
.\quirk39.exe
wnsprintfA() returned string '+00001252| 850|0X0111104E|€‚ƒ„… |0xdeadbeef' of 44 charactersOOPS¹: contrary to the second 2 highlighted statements of the documentation cited above, the
wnsprintfA()
function but supports the pointer type p
as well as the
flags +
, 0
,
(blank)
and #
!
Create the text file quirk39.xml
with the following
content next to the console application quirk39.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk39' processorArchitecture='*' type='win32' version='0.8.1.5' />
<application xmlns='urn:schemas-microsoft-com:asm.v3'>
<windowsSettings>
<activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
</windowsSettings>
</application>
<compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
<application>
<supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
</application>
</compatibility>
<description>Quirk39 Console Application</description>
</assembly>
Note: the double use of an
XML element
named application
is (at least) clumsy and error-prone!
Embed the
Application Manifest
quirk39.xml
created in step 4. in the console
application quirk39.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk39.xml /OUTPUTRESOURCE:quirk39.exeNote: the Manifest Tool
MT.exe
is shipped with the Windows Software Development Kit.
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application quirk39.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the (mis)behaviour and prove the documentation cited
above wrong:
VER .\quirk39.exe
Microsoft Windows [Version 10.0.22621.1105] wnsprintfA() returned string '+00065001| 65001|0X0017104E|€‚ƒ„… |0xdeadbeef' of 55 characters wnsprintfA() returned string '€' of 3 characters for wide character '€' (U+20AC) wnsprintfA() returned string '‚' of 3 characters for wide character '‚' (U+201A) wnsprintfA() returned string 'ƒ' of 2 characters for wide character 'ƒ' (U+0192) wnsprintfA() returned string '„' of 3 characters for wide character '„' (U+201E) wnsprintfA() returned string '…' of 3 characters for wide character '…' (U+2026) wnsprintfA() returned string '†' of 3 characters for wide character '†' (U+2020) wnsprintfA() returned string '‡' of 3 characters for wide character '‡' (U+2021) wnsprintfA() returned string 'ˆ' of 2 characters for wide character 'ˆ' (U+02C6) wnsprintfA() returned string '‰' of 3 characters for wide character '‰' (U+2030) wnsprintfA() returned string 'Š' of 2 characters for wide character 'Š' (U+0160) wnsprintfA() returned string '‹' of 3 characters for wide character '‹' (U+2039) wnsprintfA() returned string 'Œ' of 2 characters for wide character 'Œ' (U+0152) wnsprintfA() returned string 'Ž' of 2 characters for wide character 'Ž' (U+017D) wnsprintfA() returned string '‘' of 3 characters for wide character '‘' (U+2018) wnsprintfA() returned string '’' of 3 characters for wide character '’' (U+2019) wnsprintfA() returned string '“' of 3 characters for wide character '“' (U+201C) wnsprintfA() returned string '”' of 3 characters for wide character '”' (U+201D) wnsprintfA() returned string '•' of 3 characters for wide character '•' (U+2022) wnsprintfA() returned string '–' of 3 characters for wide character '–' (U+2013) wnsprintfA() returned string '—' of 3 characters for wide character '—' (U+2014) wnsprintfA() returned string '˜' of 2 characters for wide character '˜' (U+02DC) wnsprintfA() returned string '™' of 3 characters for wide character '™' (U+2122) wnsprintfA() returned string 'š' of 2 characters for wide character 'š' (U+0161) wnsprintfA() returned string '›' of 3 characters for wide character '›' (U+203A) wnsprintfA() returned string 'œ' of 2 characters for wide character 'œ' (U+0153) wnsprintfA() returned string 'ž' of 2 characters for wide character 'ž' (U+017E) wnsprintfA() returned string 'Ÿ' of 2 characters for wide character 'Ÿ' (U+0178) wnsprintfA() returned string ' ' of 2 characters for wide character ' ' (U+00A0) wnsprintfA() returned string '¡' of 2 characters for wide character '¡' (U+00A1) wnsprintfA() returned string '¢' of 2 characters for wide character '¢' (U+00A2) wnsprintfA() returned string '£' of 2 characters for wide character '£' (U+00A3) wnsprintfA() returned string '¤' of 2 characters for wide character '¤' (U+00A4) wnsprintfA() returned string '¥' of 2 characters for wide character '¥' (U+00A5) wnsprintfA() returned string '¦' of 2 characters for wide character '¦' (U+00A6) wnsprintfA() returned string '§' of 2 characters for wide character '§' (U+00A7) wnsprintfA() returned string '¨' of 2 characters for wide character '¨' (U+00A8) wnsprintfA() returned string '©' of 2 characters for wide character '©' (U+00A9) wnsprintfA() returned string 'ª' of 2 characters for wide character 'ª' (U+00AA) wnsprintfA() returned string '«' of 2 characters for wide character '«' (U+00AB) wnsprintfA() returned string '¬' of 2 characters for wide character '¬' (U+00AC) wnsprintfA() returned string '' of 2 characters for wide character '' (U+00AD) wnsprintfA() returned string '®' of 2 characters for wide character '®' (U+00AE) wnsprintfA() returned string '¯' of 2 characters for wide character '¯' (U+00AF) wnsprintfA() returned string '°' of 2 characters for wide character '°' (U+00B0) wnsprintfA() returned string '±' of 2 characters for wide character '±' (U+00B1) wnsprintfA() returned string '²' of 2 characters for wide character '²' (U+00B2) wnsprintfA() returned string '³' of 2 characters for wide character '³' (U+00B3) wnsprintfA() returned string '´' of 2 characters for wide character '´' (U+00B4) wnsprintfA() returned string 'µ' of 2 characters for wide character 'µ' (U+00B5) wnsprintfA() returned string '¶' of 2 characters for wide character '¶' (U+00B6) wnsprintfA() returned string '·' of 2 characters for wide character '·' (U+00B7) wnsprintfA() returned string '¸' of 2 characters for wide character '¸' (U+00B8) wnsprintfA() returned string '¹' of 2 characters for wide character '¹' (U+00B9) wnsprintfA() returned string 'º' of 2 characters for wide character 'º' (U+00BA) wnsprintfA() returned string '»' of 2 characters for wide character '»' (U+00BB) wnsprintfA() returned string '¼' of 2 characters for wide character '¼' (U+00BC) wnsprintfA() returned string '½' of 2 characters for wide character '½' (U+00BD) wnsprintfA() returned string '¾' of 2 characters for wide character '¾' (U+00BE) wnsprintfA() returned string '¿' of 2 characters for wide character '¿' (U+00BF) wnsprintfA() returned string 'À' of 2 characters for wide character 'À' (U+00C0) wnsprintfA() returned string 'Á' of 2 characters for wide character 'Á' (U+00C1) wnsprintfA() returned string 'Â' of 2 characters for wide character 'Â' (U+00C2) wnsprintfA() returned string 'Ã' of 2 characters for wide character 'Ã' (U+00C3) wnsprintfA() returned string 'Ä' of 2 characters for wide character 'Ä' (U+00C4) wnsprintfA() returned string 'Å' of 2 characters for wide character 'Å' (U+00C5) wnsprintfA() returned string 'Æ' of 2 characters for wide character 'Æ' (U+00C6) wnsprintfA() returned string 'Ç' of 2 characters for wide character 'Ç' (U+00C7) wnsprintfA() returned string 'È' of 2 characters for wide character 'È' (U+00C8) wnsprintfA() returned string 'É' of 2 characters for wide character 'É' (U+00C9) wnsprintfA() returned string 'Ê' of 2 characters for wide character 'Ê' (U+00CA) wnsprintfA() returned string 'Ë' of 2 characters for wide character 'Ë' (U+00CB) wnsprintfA() returned string 'Ì' of 2 characters for wide character 'Ì' (U+00CC) wnsprintfA() returned string 'Í' of 2 characters for wide character 'Í' (U+00CD) wnsprintfA() returned string 'Î' of 2 characters for wide character 'Î' (U+00CE) wnsprintfA() returned string 'Ï' of 2 characters for wide character 'Ï' (U+00CF) wnsprintfA() returned string 'Ð' of 2 characters for wide character 'Ð' (U+00D0) wnsprintfA() returned string 'Ñ' of 2 characters for wide character 'Ñ' (U+00D1) wnsprintfA() returned string 'Ò' of 2 characters for wide character 'Ò' (U+00D2) wnsprintfA() returned string 'Ó' of 2 characters for wide character 'Ó' (U+00D3) wnsprintfA() returned string 'Ô' of 2 characters for wide character 'Ô' (U+00D4) wnsprintfA() returned string 'Õ' of 2 characters for wide character 'Õ' (U+00D5) wnsprintfA() returned string 'Ö' of 2 characters for wide character 'Ö' (U+00D6) wnsprintfA() returned string '×' of 2 characters for wide character '×' (U+00D7) wnsprintfA() returned string 'Ø' of 2 characters for wide character 'Ø' (U+00D8) wnsprintfA() returned string 'Ù' of 2 characters for wide character 'Ù' (U+00D9) wnsprintfA() returned string 'Ú' of 2 characters for wide character 'Ú' (U+00DA) wnsprintfA() returned string 'Û' of 2 characters for wide character 'Û' (U+00DB) wnsprintfA() returned string 'Ü' of 2 characters for wide character 'Ü' (U+00DC) wnsprintfA() returned string 'Ý' of 2 characters for wide character 'Ý' (U+00DD) wnsprintfA() returned string 'Þ' of 2 characters for wide character 'Þ' (U+00DE) wnsprintfA() returned string 'ß' of 2 characters for wide character 'ß' (U+00DF) wnsprintfA() returned string 'à' of 2 characters for wide character 'à' (U+00E0) wnsprintfA() returned string 'á' of 2 characters for wide character 'á' (U+00E1) wnsprintfA() returned string 'â' of 2 characters for wide character 'â' (U+00E2) wnsprintfA() returned string 'ã' of 2 characters for wide character 'ã' (U+00E3) wnsprintfA() returned string 'ä' of 2 characters for wide character 'ä' (U+00E4) wnsprintfA() returned string 'å' of 2 characters for wide character 'å' (U+00E5) wnsprintfA() returned string 'æ' of 2 characters for wide character 'æ' (U+00E6) wnsprintfA() returned string 'ç' of 2 characters for wide character 'ç' (U+00E7) wnsprintfA() returned string 'è' of 2 characters for wide character 'è' (U+00E8) wnsprintfA() returned string 'é' of 2 characters for wide character 'é' (U+00E9) wnsprintfA() returned string 'ê' of 2 characters for wide character 'ê' (U+00EA) wnsprintfA() returned string 'ë' of 2 characters for wide character 'ë' (U+00EB) wnsprintfA() returned string 'ì' of 2 characters for wide character 'ì' (U+00EC) wnsprintfA() returned string 'í' of 2 characters for wide character 'í' (U+00ED) wnsprintfA() returned string 'î' of 2 characters for wide character 'î' (U+00EE) wnsprintfA() returned string 'ï' of 2 characters for wide character 'ï' (U+00EF) wnsprintfA() returned string 'ð' of 2 characters for wide character 'ð' (U+00F0) wnsprintfA() returned string 'ñ' of 2 characters for wide character 'ñ' (U+00F1) wnsprintfA() returned string 'ò' of 2 characters for wide character 'ò' (U+00F2) wnsprintfA() returned string 'ó' of 2 characters for wide character 'ó' (U+00F3) wnsprintfA() returned string 'ô' of 2 characters for wide character 'ô' (U+00F4) wnsprintfA() returned string 'õ' of 2 characters for wide character 'õ' (U+00F5) wnsprintfA() returned string 'ö' of 2 characters for wide character 'ö' (U+00F6) wnsprintfA() returned string '÷' of 2 characters for wide character '÷' (U+00F7) wnsprintfA() returned string 'ø' of 2 characters for wide character 'ø' (U+00F8) wnsprintfA() returned string 'ù' of 2 characters for wide character 'ù' (U+00F9) wnsprintfA() returned string 'ú' of 2 characters for wide character 'ú' (U+00FA) wnsprintfA() returned string 'û' of 2 characters for wide character 'û' (U+00FB) wnsprintfA() returned string 'ü' of 2 characters for wide character 'ü' (U+00FC) wnsprintfA() returned string 'ý' of 2 characters for wide character 'ý' (U+00FD) wnsprintfA() returned string 'þ' of 2 characters for wide character 'þ' (U+00FE) wnsprintfA() returned string 'ÿ' of 2 characters for wide character 'ÿ' (U+00FF)OOPS²: contrary to the second 2 highlighted statements of the documentation cited above, the
wnsprintfA()
function but supports the pointer type p
as well as the
flags +
, 0
,
(blank)
and #
!
OOPS³: for each of the 123 wide characters
from the string CP1252
, the
wnsprintfA()
function returns a character count greater 1, i.e. the documentation
cited above confuses character alias code point and
byte alias code unit!
Aforms of the Win32 functions for directory and file management are all limited to a maximum path name length of 260 characters, defined with the preprocessor macro
MAX_PATH
. For example, the
RemoveDirectoryA()
function is documented in the
MSDN as
follows:
Deletes an existing empty directory.The[…]
[…]BOOL RemoveDirectoryA( LPCSTR lpPathName );
lpPathName
The path of the directory to be removed. This path must specify an empty directory, and the calling process must have delete access to the directory.
In the ANSI version of this function, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, call the Unicode version of the function (RemoveDirectoryW), and prepend "\\?\" to the path. For more information, see Naming Files, Paths, and Namespaces.
Tip Starting in Windows 10, version 1607, for the unicode version of this function (RemoveDirectoryW), you can opt-in to remove the MAX_PATH character limitation without prepending "\\?\". See the "Maximum Path Limitation" section of Naming Files, Paths, and Namespaces for details.
CreateDirectoryA()
function is documented in the
MSDN with an
additional limitation, giving but a wrong number:
Creates a new directory. […]OUCH: the correct value of the string size limit is[…]BOOL CreateDirectoryA( LPCSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttributes );
lpPathName
The path of the directory to be created.
For the ANSI version of this function, there is a default string size limit for paths of 248 characters (MAX_PATH - enough room for a 8.3 filename). To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. For more information, see Naming Files, Paths, and Namespaces.
Tip Starting with Windows 10, version 1607, for the unicode version of this function (CreateDirectoryW), you can opt-in to remove the 248 character limitation without prepending "\\?\". The 255 character limit per path segment still applies. See the "Maximum Path Length Limitation" section of Naming Files, Paths, and Namespaces for details.
lpSecurityAttributes
[…]
MAX_PATH
− sizeof("\\filename.ext")
= 260 − 14 = 246!
Win32 APIs often support both -A and -W variants.Caveat: here character means-A variants recognize the ANSI code page configured on the system and support
char*
, while -W variants operate in UTF-16 and supportWCHAR
.Until recently, Windows has emphasized "Unicode" -W variants over -A APIs. However, recent releases have used the ANSI code page and -A APIs as a means to introduce UTF-8 support to apps. If the ANSI code page is configured for UTF-8, -A APIs typically operate in UTF-8. This model has the benefit of supporting existing code built with -A APIs without any code changes.
[…]
Note
CP_ACP
equates toCP_UTF8
only if running on Windows Version 1903 (May 2019 Update) or above and the ActiveCodePage property described above is set to UTF-8. Otherwise, it honors the legacy system code page. We recommend usingCP_UTF8
explicitly.
CHAR
alias char
alias byte, not
Unicode
code point!
Since in UTF-8 encoding a code point requires 1 to 4 code units alias characters, configuring the ANSI code page for UTF-8 can actually break existing code!
Note: in UTF-16 encoding a code point requires 1 or 2 code units alias wide characters of 2 characters each; while code points which fit into 1 UTF-16 code unit require 1 to 3 UTF-8 code units of 1 character each, code points which require 2 UTF-16 code units, a high/low surrogate pair, require 4 UTF-8 code units, i.e. the latter require in both transformation formats the same number of characters alias bytes.
Create the text file quirk40.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define _CRT_SECURE_NO_WARNINGS
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Thai, Baht
// CP874 L"฿\\๐๑๒๓๔๕๖๗๘๙…"
// Japanese, Yen
// CP932 L"¥\\〇一二三四五六七八九…"
// Chinese, Yuan Renminbi
// CP936 L"元\\零一二三四五六七八九…"
// Korean, Won
// CP949 L"₩\\…"
// Traditional Chinese
// CP950 L"…\\…"
// Central/Eastern Europe, Azerbaijani Manat
// CP1250 L"₼\\…"
// Cyrillic, Russian Ruble
// CP1251 L"₽\\ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯёђѓєѕіїјљњћќѝўџабвгдежзийклмнопрстуфхцчшщъыьэюя…"
// Western, Euro
#define CP1252 L"€\\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
// Greek, Drachma
// CP1253 L"₯\\ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω…"
// Turkish, Lira
// CP1254 L"₻\\…"
// Hebrew, New Shekel
// CP1255 L"₪\\אבגדהוזחטיךכלםמןנסעףפץצקרשת…"
// Arabic, Iranian Rial
// CP1256 L"﷼\\٠١٢٣٤٥٦٧٨٩…"
// Baltic, Euro
// CP1257 L"€\\…"
// Vietnamese, Dong
// CP1258 L"₫\\空𠬠𠄩𠀧𦊚𠄼𦒹𦉱𠔭𠃩𨒒…"
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WIN32_FIND_DATAA wfd;
DWORD dwError = ERROR_SUCCESS;
CHAR szEURO[4];
#ifdef QUIRKS
CHAR szANSI[MAX_PATH * 3];
#else
CHAR szANSI[MAX_PATH];
#endif
WCHAR szWide[MAX_PATH];
UINT uiWide;
UINT uiANSI;
HANDLE hANSI;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
if (WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
L"€", sizeof(L"€") / sizeof(L'€'),
szEURO, sizeof(szEURO),
(LPCCH) NULL, (LPBOOL) NULL) == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu\n",
dwError = GetLastError());
else
if (!CreateDirectoryA(szEURO, (LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateDirectoryA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szEURO);
else
{
if (WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CP1252, sizeof(CP1252) / sizeof(*CP1252),
szANSI, sizeof(szANSI),
(LPCCH) NULL, (LPBOOL) NULL) == 0)
{
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for pathname \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), CP1252, wcslen(CP1252));
if (!CreateDirectoryW(CP1252, (LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateDirectoryW() returned error %lu for pathname \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), CP1252, wcslen(CP1252));
wcscpy(szWide, L"€\\");
do
wcscat(szWide, L"€");
while (CreateDirectoryW(szWide, (LPSECURITY_ATTRIBUTES) NULL));
PrintConsole(hConsole,
L"CreateDirectoryW() returned error %lu for pathname \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), szWide, wcslen(szWide));
}
else
{
if (!CreateDirectoryA(szANSI, (LPSECURITY_ATTRIBUTES) NULL))
PrintConsole(hConsole,
L"CreateDirectoryA() returned error %lu for pathname \'%hs\' of %lu characters\n",
dwError = GetLastError(), szANSI, strlen(szANSI));
else
if (GetTempFileNameA(szANSI, szEURO, 0, szANSI) == 0)
PrintConsole(hConsole,
L"GetTempFileNameA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
else
{
PrintConsole(hConsole,
L"GetTempFileNameA() returned pathname \'%hs\' of %lu characters\n",
szANSI, strlen(szANSI));
if (!DeleteFileA(szANSI))
PrintConsole(hConsole,
L"DeleteFileA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
}
strcpy(szANSI, szEURO);
strcat(szANSI, "\\");
do
strcat(szANSI, szEURO);
while (CreateDirectoryA(szANSI, (LPSECURITY_ATTRIBUTES) NULL));
PrintConsole(hConsole,
L"CreateDirectoryA() returned error %lu for pathname \'%hs\' of %lu characters\n",
dwError = GetLastError(), szANSI, strlen(szANSI));
}
uiANSI = WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CP1252, 4,
szANSI, sizeof(szANSI),
(LPCCH) NULL, (LPBOOL) NULL);
if (uiANSI == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for pathname \'%.4ls\' of 4 wide characters\n",
dwError = GetLastError(), CP1252);
else
{
strcpy(szANSI + uiANSI, "*");
hANSI = FindFirstFileA(szANSI, &wfd);
if (hANSI == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"FindFirstFileA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
else
{
while (FindNextFileA(hANSI, &wfd));
dwError = GetLastError();
if (dwError != ERROR_NO_MORE_FILES)
PrintConsole(hConsole,
L"FindNextFileA() returned error %lu\n",
dwError);
}
}
strcpy(szANSI, szEURO);
strcat(szANSI, "\\*");
hANSI = FindFirstFileA(szANSI, &wfd);
if (hANSI == INVALID_HANDLE_VALUE)
PrintConsole(hConsole,
L"FindFirstFileA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
else
{
do
{
strcpy(szANSI, szEURO);
strcat(szANSI, "\\");
strcat(szANSI, wfd.cFileName);
if (!RemoveDirectoryA(szANSI))
PrintConsole(hConsole,
L"RemoveDirectoryA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
} while (FindNextFileA(hANSI, &wfd));
dwError = GetLastError();
if (dwError == ERROR_MORE_DATA)
{
uiWide = MultiByteToWideChar(CP_ACP,
MB_ERR_INVALID_CHARS | MB_PRECOMPOSED,
szANSI, -1,
(LPWSTR) NULL, 0);
if (uiWide == 0)
PrintConsole(hConsole,
L"MultiByteToWideChar() returned error %lu\n",
dwError = GetLastError());
PrintConsole(hConsole,
L"FindNextFileA() returned pathname \'%hs\' of %lu characters in %u code points\n",
szANSI, strlen(szANSI), uiWide - 1);
}
if (dwError != ERROR_NO_MORE_FILES)
PrintConsole(hConsole,
L"FindNextFileA() returned error %lu\n",
dwError);
else
if (GetTempFileNameA(szANSI, szEURO, 0, szANSI) == 0)
PrintConsole(hConsole,
L"GetTempFileNameA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
else
{
PrintConsole(hConsole,
L"GetTempFileNameA() returned pathname \'%hs\' of %lu characters\n",
szANSI, strlen(szANSI));
if (!DeleteFileA(szANSI))
PrintConsole(hConsole,
L"DeleteFileA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szANSI);
}
if (!FindClose(hANSI))
PrintConsole(hConsole,
L"FindClose() returned error %lu\n",
GetLastError());
}
if (!RemoveDirectoryA(szEURO))
PrintConsole(hConsole,
L"RemoveDirectoryA() returned error %lu for pathname \'%hs\'\n",
dwError = GetLastError(), szEURO);
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Code Page 1250 Windows Latin 2 (Central Europe)
Code Page 1251 Windows Cyrillic (Slavic)
Code Page 1252 Windows Latin 1 (ANSI)
Code Page 1253 Windows Greek
Code Page 1254 Windows Latin 5 (Turkish)
Code Page 1255 Windows Hebrew
Code Page 1256 Windows Arabic
Code Page 1257 Windows Baltic Rim
Build the console application quirk40.exe
from the
source file quirk40.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk40.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk40.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk40.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk40.exe quirk40.obj kernel32.lib user32.lib
Execute the console application quirk40.exe
built in
step 2. to demonstrate the legacy behaviour:
CHDIR .\quirk40.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
C:\Users\Stefan GetTempFileNameA() returned pathname '€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\€20AC.tmp' of 135 characters CreateDirectoryA() returned error 206 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 232 characters RemoveDirectoryA() returned error 145 for pathname '€\.' RemoveDirectoryA() returned error 32 for pathname '€\..' FindNextFileA() returned pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 231 characters GetTempFileNameA() returned error 267 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' 0x10B (WIN32: 267 ERROR_DIRECTORY) -- 267 (267) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.OUCH¹: contrary to the documentation cited above, the
CreateDirectoryA()
function accepts only 231 characters instead of OUCH²: although the directory name for the
GetTempFileName()
function is valid and not longer than
MAX_PATH
− 14 = 246
characters, it fails with Win32 error code 267 alias
ERROR_DIRECTORY
!
Note: the first 3 error messages which show the
Win32 error codes 206 alias
ERROR_FILENAME_EXCED_RANGE
,
145 alias
ERROR_DIR_NOT_EMPTY
and 32 alias
ERROR_SHARING_VIOLATION
are expected and normal behaviour!
Create the text file quirk40.xml
with the following
content next to the console application quirk40.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk40' processorArchitecture='*' type='win32' version='0.8.1.5' />
<application xmlns='urn:schemas-microsoft-com:asm.v3'>
<windowsSettings>
<activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
</windowsSettings>
</application>
<compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
<application>
<supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
</application>
</compatibility>
<description>Quirk40 Console Application</description>
</assembly>
Note: the double use of an
XML element
named application
is (at least) clumsy and error-prone!
Embed the
Application Manifest
quirk40.xml
created in step 4. in the console
application quirk40.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk40.xml /OUTPUTRESOURCE:quirk40.exeNote: the Manifest Tool
MT.exe
is shipped with the Windows Software Development Kit.
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application quirk40.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the (mis)behaviour:
VER .\quirk40.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 10.0.22621.1105] WideCharToMultiByte() returned error 122 for pathname '€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' of 125 characters CreateDirectoryW() returned error 206 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 232 wide characters FindFirstFileA() returned error 234 for pathname '€\€‚*' RemoveDirectoryA() returned error 145 for pathname '€\.' RemoveDirectoryA() returned error 32 for pathname '€\..' FindNextFileA() returned pathname '€\€' of 7 characters in 3 code points FindNextFileA() returned error 234 RemoveDirectoryA() returned error 145 for pathname '€' 0x91 (WIN32: 145 ERROR_DIR_NOT_EMPTY) -- 145 (145) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.Note: the path name
€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
of 125
ANSI
characters as well as 125
Unicode
code points requires 266 code units alias characters in
UTF-8
encoding and exceeds MAX_PATH
= 260; its file
or directory name of 123
ANSI
characters requires 262 code units alias characters in
UTF-8
encoding and exceeds MAX_PATH
too.
OUCH¹: the
WideCharToMultiByte()
WideCharToMultiByte()
function fails with Win32 error code 122 alias
ERROR_INSUFFICIENT_BUFFER
due to the 260 character limit!
OUCH²: since its wildcard pattern
€\€‚*
matches the above path name, the
FindFirstFile()
function fails with Win32 error code 234 alias
ERROR_MORE_DATA
due to the 260 character limit imposed by the
WIN32_FIND_DATAA
data structure!
OUCH³: since the wildcard pattern
€\*
matches the above path name, the
FindNextFile()
function too fails with Win32 error code 234 alias
ERROR_MORE_DATA
due to the 260 character limit imposed by the
WIN32_FIND_DATAA
data structure!
OUCH⁴: contrary to the highlighted statement
of the documentation cited above last,
existing unchanged code
exhibits multiple
failures when code page 65001 alias CP_UTF8
is used
– it fails to enumerate path names of more than 7 (in words:
seven)
ANSI
characters respectively 3 (in words: three)
Unicode
code points, i.e. it misses all but the first of
the total 229 subdirectories created in the directory
€
and leaves this non-empty directory with 228
empty subdirectories behind!
NOTE: not demonstrated here,
existing unchanged code
is susceptible to buffer overruns
which are impossible with other (legacy
)
ANSI
Code Pages
where the Win32 functions
FindFirstFile()
and
FindNextFile()
return at most 255 characters in the
WIN32_FIND_DATAA
data structure – the concatenation of the 2 character
directory name €\
, a 2 character drive letter
A:
or a 3 character drive path name B:\
and the longest file name fits into a buffer of 260 characters
(which is the reason why MAX_PATH
is not defined as
256).
CAVEAT: without the subdirectory
€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ
in place, the
FindNextFile()
function enumerates the first
MAX_PATH
÷ strlen(szEURO)
= 260 ÷ 3 = 86
subdirectories and returns a file name of
86 × 3 = 258 characters in the
WIN32_FIND_DATAA
data structure before it fails with Win32 error code
234 alias
ERROR_MORE_DATA
– concatenation of the 4 character directory name
€\
and this 258 character file name then overruns
the buffer of 260 characters!
Caveat: the buffer overrun also happens with the
prefixes .\
, C:
and D:\
– the only prefix which avoids it is \
!
Caveat: of course the
FindFirstFile()
function too can return a file name which causes a buffer overrun.
Build the console application quirk40.exe
a second
time from the source file quirk40.c
created in
step 1., now with the preprocessor macro QUIRKS
defined:
CL.EXE /DQUIRKS quirk40.c kernel32.lib user32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk40.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk40.exe quirk40.obj kernel32.lib user32.lib
Embed the
Application Manifest
quirk40.xml
created in step 4. in the console
application quirk40.exe
built in step 7.:
MT.EXE /CANONICALIZE /MANIFEST quirk40.xml /OUTPUTRESOURCE:quirk40.exeNote: the Manifest Tool
MT.exe
is shipped with the Windows Software Development Kit.
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application quirk40.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the changed (mis)behaviour:
VER .\quirk40.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
Microsoft Windows [Version 10.0.22621.1105] GetTempFileNameA() returned error 234 for pathname '€\€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùú' CreateDirectoryA() returned error 206 for pathname '€\€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€' of 694 characters FindFirstFileA() returned error 234 for pathname '€\€‚*' RemoveDirectoryA() returned error 145 for pathname '€\.' RemoveDirectoryA() returned error 32 for pathname '€\..' FindNextFileA() returned pathname '€\€' of 7 characters in 3 code points FindNextFileA() returned error 234 RemoveDirectoryA() returned error 145 for pathname '€' 0x91 (WIN32: 145 ERROR_DIR_NOT_EMPTY) -- 145 (145) Error message text: The directory name is invalid. CertUtil: -error command completed successfully.OOPS: on Windows 10 1903 and later versions, using the code page 65001 alias
CP_UTF8
, at
least the
CreateDirectoryA()
function is not limited to MAX_PATH
(minus some) characters any more, but supports MAX_PATH
(minus some) code points, i.e. about
MAX_PATH
× 3 = 780 characters!
OUCH¹: the
GetTempFileNameA()
function fails with Win32 error code 234 alias
ERROR_MORE_DATA
for a path name of 125 code points, truncating it to 120 code points
in the output buffer!
OUCH²: as before, the
FindFirstFile()
function fails with Win32 error code 234 alias
ERROR_MORE_DATA
due to the 260 character limit imposed by the
WIN32_FIND_DATAA
data structure!
OUCH³: as before, the
FindNextFile()
function fails with Win32 error code 234 alias
ERROR_MORE_DATA
due to the 260 character limit imposed by the
WIN32_FIND_DATAA
data structure, after enumerating only 1 of 229 subdirectory names!
ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω
from code page 1253 or non-ASCII characters from other
(legacy) Code Pages is left as an exercise to the reader.
Note: a repetition of this demonstration in the 64-bit execution environment is left as an exercise to the reader.
GetClassNameA()
,
GetWindowTextA()
,
GetWindowTextLengthA()
and
RealGetWindowClassA()
are documented in the
MSDN as
follows:
Retrieves the name of the class to which the specified window belongs.[…]int GetClassNameA( HWND hWnd, LPSTR lpClassName, int nMaxCount );
hWnd
A handle to the window and, indirectly, the class to which the window belongs.
lpClassName
The class name string.
nMaxCount
The length of the lpClassName buffer, in characters. The buffer must be large enough to include the terminating null character; otherwise, the class name string is truncated to
nMaxCount-1
characters.[…]
If the function succeeds, the return value is the number of characters copied to the buffer, not including the terminating null character.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Copies the text of the specified window's title bar (if it has one) into a buffer. […][…]int GetWindowTextA( HWND hWnd, LPSTR lpString, int nMaxCount );
hWnd
A handle to the window or control containing the text.
lpString
The buffer that will receive the text. If the string is as long or longer than the buffer, the string is truncated and terminated with a null character.
nMaxCount
The maximum number of characters to copy to the buffer, including the null character. If the text exceeds this limit, it is truncated.
[…]
If the function succeeds, the return value is the length, in characters, of the copied string, not including the terminating null character. If the window has no title bar or text, if the title bar is empty, or if the window or control handle is invalid, the return value is zero. To get extended error information, call GetLastError.
Retrieves the length, in characters, of the specified window's title bar text (if the window has a title bar). […][…]int GetWindowTextLengthA( HWND hWnd );
hWnd
A handle to the window or control.
[…]
If the function succeeds, the return value is the length, in characters, of the text. […]
If the window has no text, the return value is zero.
Retrieves a string that specifies the window type.[…]UINT RealGetWindowClassW( HWND hwnd, LPWSTR ptszClassName, UINT cchClassNameMax );
hwnd
A handle to the window whose type will be retrieved.
ptszClassName
A pointer to a string that receives the window type.
cchClassNameMax
The length, in characters, of the buffer pointed to by the pszType parameter.
[…]
If the function succeeds, the return value is the number of characters copied to the specified buffer.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
GetClassNameA()
function with code page 65001 alias CP_UTF8
.
Create the text file quirk41.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define CLASS L"€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"
#define TITLE L"€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›€‚„…†‡‰‹‘’“”•–—™›"
LRESULT WINAPI WindowProc(HWND hWindow, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
if (uMessage == WM_DESTROY)
PostQuitMessage(0);
return DefWindowProcA(hWindow, uMessage, wParam, lParam);
}
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
WNDCLASSEXA wce = {sizeof(wce),
CS_DBLCLKS,
WindowProc,
0, 0,
(HINSTANCE) &__ImageBase,
(HICON) NULL,
(HCURSOR) NULL,
(HBRUSH) COLOR_BACKGROUND,
(LPCSTR) NULL,
(LPCSTR) NULL,
(HICON) NULL};
ATOM atom;
MSG msg;
HWND hWindow;
DWORD dwError;
UINT uiClass;
CHAR szClass[256 * 3];
UINT uiTitle;
CHAR szTitle[256 * 3];
UINT uiQuirk;
CHAR szQuirk[256 * 3];
BOOL bResult;
LRESULT lResult;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
uiClass = WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
CLASS, sizeof(CLASS) / sizeof(*CLASS),
szClass, sizeof(szClass),
(LPCCH) NULL, (LPBOOL) NULL);
if (uiClass == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for class name \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), CLASS, wcslen(CLASS));
else
{
uiTitle = WideCharToMultiByte(CP_ACP,
WC_COMPOSITECHECK | WC_DEFAULTCHAR | WC_NO_BEST_FIT_CHARS,
TITLE, sizeof(TITLE) / sizeof(*TITLE),
szTitle, sizeof(szTitle),
(LPCCH) NULL, (LPBOOL) NULL);
if (uiTitle == 0)
PrintConsole(hConsole,
L"WideCharToMultiByte() returned error %lu for window title \'%ls\' of %lu wide characters\n",
dwError = GetLastError(), TITLE, wcslen(TITLE));
else
{
wce.lpszClassName = szClass;
atom = RegisterClassExA(&wce);
if (atom == 0)
PrintConsole(hConsole,
L"RegisterClassExA() returned error %lu\n",
dwError = GetLastError());
else
{
hWindow = CreateWindowExA(WS_EX_APPWINDOW,
szClass,
szTitle,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
(HWND) NULL,
(HMENU) NULL,
(HINSTANCE) &__ImageBase,
NULL);
if (hWindow == NULL)
PrintConsole(hConsole,
L"CreateWindowExA() returned error %lu\n",
dwError = GetLastError());
else
{
uiQuirk = GetWindowTextLengthA(hWindow);
if (uiQuirk == 0)
PrintConsole(hConsole,
L"GetWindowTextLengthA() returned error %lu\n",
dwError = GetLastError());
else
PrintConsole(hConsole,
L"GetWindowTextLengthA() returned %u\n",
uiQuirk);
uiQuirk = GetWindowTextA(hWindow, szQuirk, sizeof(szQuirk));
if (uiQuirk == 0)
PrintConsole(hConsole,
L"GetWindowTextA() returned error %lu\n",
dwError = GetLastError());
else
if (memcmp(szQuirk, szTitle, uiTitle) != 0)
PrintConsole(hConsole,
L"GetWindowTextA() returned DIFFERENT window title \'%hs\' of %u characters\n",
szQuirk, uiQuirk);
uiQuirk = GetClassNameA(hWindow, szQuirk, sizeof(szQuirk));
if (uiQuirk == 0)
PrintConsole(hConsole,
L"GetClassNameA() returned error %lu\n",
dwError = GetLastError());
else
if (memcmp(szQuirk, szClass, uiClass) != 0)
PrintConsole(hConsole,
L"GetClassNameA() returned DIFFERENT class name \'%hs\' of %u characters\n",
szQuirk, uiQuirk);
uiQuirk = RealGetWindowClassA(hWindow, szQuirk, sizeof(szQuirk));
if (uiQuirk == 0)
PrintConsole(hConsole,
L"RealGetWindowClassA() returned error %lu\n",
dwError = GetLastError());
else
if (memcmp(szQuirk, szClass, uiClass) != 0)
PrintConsole(hConsole,
L"RealGetWindowClassA() returned DIFFERENT class name \'%hs\' of %u characters\n",
szQuirk, uiQuirk);
while ((bResult = GetMessageA(&msg, (HWND) NULL, 0, 0)) > 0)
{
if (TranslateMessage(&msg))
;
lResult = DispatchMessageA(&msg);
}
dwError = bResult < 0 ? GetLastError() : msg.wParam;
}
if (!UnregisterClassA(szClass, (HINSTANCE) &__ImageBase))
PrintConsole(hConsole,
L"UnregisterClassA() returned error %lu\n",
dwError = GetLastError());
}
}
}
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Note: the character strings CLASS
and
TITLE
contain 123 respectively
15 × 17 = 255 (wide) characters,
equivalent to 123 and 255
ANSI
characters in 123 and 255 bytes or 123 and 255
Unicode
code points in
123 × 2 + 17 = 263 and
255 × 3 = 765
UTF-8
code units alias bytes.
Build the console application quirk41.exe
from the
source file quirk41.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk41.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk41.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk41.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk41.exe quirk41.obj kernel32.lib user32.lib
Execute the console application quirk41.exe
built in
step 2. to show the behaviour with the legacy
code page,
then close its window:
VER .\quirk41.exe
Microsoft Windows [Version 10.0.22621.1105] GetWindowTextLengthA() returned 255
Create the text file quirk41.xml
with the following
content next to the console application quirk41.exe
built in step 2.:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk41' processorArchitecture='*' type='win32' version='0.8.1.5' />
<application xmlns='urn:schemas-microsoft-com:asm.v3'>
<windowsSettings>
<activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
</windowsSettings>
</application>
<compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
<application>
<supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
</application>
</compatibility>
<description>Quirk41 Console Application</description>
</assembly>
Note: the double use of an
XML element
named application
is (at least) clumsy and error-prone!
Embed the
Application Manifest
quirk41.xml
created in step 4. in the console
application quirk41.exe
built in step 2.:
MT.EXE /CANONICALIZE /MANIFEST quirk41.xml /OUTPUTRESOURCE:quirk41.exeNote: the Manifest Tool
MT.exe
is shipped with the Windows Software Development Kit.
Microsoft (R) Manifest Tool version 6.1.7716.0 Copyright (c) Microsoft Corporation 2009. All rights reserved.
Execute the console application quirk41.exe
configured
in step 5. for the code page 65001 alias CP_UTF
to
demonstrate the (mis)behaviour
bug, then close its window:
VER .\quirk41.exe
Microsoft Windows [Version 10.0.22621.1105]
GetWindowTextLengthA() returned 765
GetClassNameA() returned DIFFERENT class name '€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö' of 245 characters
OUCH: the Win32 function
GetClassNameA()
returns a truncated character string of only 114
code points in 245 code units instead of 123 code points in 263
code units!
application manifestWindows exhibits braindead (mis)behaviour!
Create the text file quirk42.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
DWORD dwError = ERROR_SUCCESS;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
PrintConsole(hConsole,
L"GetACP() returned %u\n"
L"GetConsoleCP() returned %u\n"
L"GetConsoleOutputCP() returned %u\n"
L"GetOEMCP() returned %u\n",
GetACP(), GetConsoleCP(), GetConsoleOutputCP(), GetOEMCP());
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
dwError = GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk42.exe
from the
source file quirk42.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk42.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk42.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk42.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk42.exe quirk42.obj kernel32.lib user32.lib
Create an empty external
application manifest
quirk42.exe.manifest
next
to the console application quirk42.exe
built in
step 2. and execute quirk42.exe
a first time:
COPY NUL: quirk42.exe.manifest .\quirk42.exe
1 file(s) copied. The system cannot execute the specified program.Note: the empty external
application manifest
quirk42.exe.manifest
shows
the expected and intended effect – it lets the console
application quirk42.exe
fail to load!
Remove the empty external application manifest
quirk42.exe.manifest
and execute the console
application quirk42.exe
a second time:
ERASE quirk42.exe.manifest .\quirk42.exe
GetACP() returned 1252 GetConsoleCP() returned 858 GetConsoleOutputCP() returned 858 GetOEMCP() returned 850OOPS: without (external)
application manifest, the console application
quirk42.exe
uses the legacyCode Page 858 alias
OEM Multilingual Latin 1 + Euro Symbol, which encodes
€
as code point
0xD5 = 213, not the system’s global
OEM
code page 850 alias Multilingual (Latin I)or
OEM Multilingual Latin 1; Western European (DOS), which encodes
ı
as code point
0xD5 = 213!
Create the external application manifest
quirk42.exe.manifest
with the following content:
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<!-- Copyright (C) 2004-2025, Stefan Kanthak -->
<assembly manifestVersion='1.0' xmlns='urn:schemas-microsoft-com:asm.v1'>
<assemblyIdentity name='Quirk42' processorArchitecture='*' type='win32' version='0.8.1.5' />
<application xmlns='urn:schemas-microsoft-com:asm.v3'>
<windowsSettings>
<activeCodePage xmlns='http://schemas.microsoft.com/SMI/2019/WindowsSettings'>UTF-8</activeCodePage>
</windowsSettings>
</application>
<compatibility xmlns='urn:schemas-microsoft-com:compatibility.v1'>
<application>
<supportedOS Id='{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}' />
</application>
</compatibility>
<description>Quirk42 Console Application</description>
</assembly>
Execute the console application quirk42.exe
a third
time:
.\quirk42.exe
GetACP() returned 1252 GetConsoleCP() returned 858 GetConsoleOutputCP() returned 858 GetOEMCP() returned 850OUCH¹: the external
application manifest
quirk42.exe.manifest
shows
no effect, it is but ignored!
Rename the console application quirk42.exe
and its
external application manifest
quirk42.exe.manifest
to quirk42.com
and
quirk42.com.manifest
respectively, then execute the
console application quirk42.com
a fourth time:
RENAME quirk42.exe quirk42.com RENAME quirk42.exe.manifest quirk42.com.manifest .\quirk42.com
GetACP() returned 65001 GetConsoleCP() returned 65001 GetConsoleOutputCP() returned 65001 GetOEMCP() returned 65001Note: the external
application manifest
quirk42.com.manifest
shows the expected and intended
effect now – the console application quirk42.com
uses the code page 65001 alias CP_UTF8
!
Overwrite the external application manifest
quirk42.com.manifest
with an empty file and execute the
console application quirk42.com
a fifth time:
COPY /Y NUL: quirk42.com.manifest .\quirk42.com
1 file(s) copied. GetACP() returned 65001 GetConsoleCP() returned 65001 GetConsoleOutputCP() returned 65001 GetOEMCP() returned 65001OUCH²: the (empty) external
application manifestshows no effect any more, it is but ignored!
Remove the empty external application manifest
quirk42.com.manifest
and execute the
application quirk42.com
a last time:
ERASE quirk42.com.manifest .\quirk42.com
GetACP() returned 65001 GetConsoleCP() returned 65001 GetConsoleOutputCP() returned 65001 GetOEMCP() returned 65001OUCH³: the absence of an external
application manifesttoo shows no effect!
GetOverlappedResult()
and the
OVERLAPPED
structure are documented in the
MSDN as
follows:
Retrieves the results of an overlapped operation on the specified file, named pipe, or communications device. […][…]BOOL GetOverlappedResult( HANDLE hFile, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait )
hFile
A handle to the file, named pipe, or communications device. […]
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
MembersNote: status code means NTSTATUS code. NTSTATUS values
Internal
The status code for the I/O request. When the request is issued, the system sets this member to STATUS_PENDING to indicate that the operation has not yet started. When the request is completed, the system sets this member to the status code for the completed request.
InternalHigh
The number of bytes transferred for the I/O request. The system sets this member if the request is completed without errors.
[…]
hEvent
A handle to the event that will be set to a signaled state by the system when the operation has completed. The user must initialize this member either to zero or a valid event handle using the CreateEvent function before passing this structure to any overlapped functions. This event can then be used to synchronize simultaneous I/O requests for a device. […]
Create the text file quirk46.c
with the following
content in an arbitrary, preferable empty directory:
// Copyright © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
#define STRICT
#define UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define STATUS_SUCCESS 0x00000000
#define STATUS_WAIT_1 0x00000001
#define STATUS_WAIT_2 0x00000002
#define STATUS_WAIT_3 0x00000003
#define STATUS_WAIT_63 0x0000003F
#define STATUS_ABANDONED_WAIT_63 0x000000BF
#define STATUS_ALREADY_COMPLETE 0x000000FF
#define STATUS_OBJECT_NAME_EXISTS 0x40000000
#define STATUS_THREAD_WAS_SUSPENDED 0x40000001
#define STATUS_ALREADY_WIN32 0x4000001B
#define STATUS_BUFFER_OVERFLOW 0x80000005
#define STATUS_NO_MORE_FILES 0x80000006
#define STATUS_UNSUCCESSFUL 0xC0000001
#define STATUS_NOT_IMPLEMENTED 0xC0000002
#define STATUS_INVALID_INFO_CLASS 0xC0000003
#define STATUS_UNHANDLED_EXCEPTION 0xC0000144
#define STATUS_SYSTEM_PROCESS_TERMINATED 0xC000021A
#define STATUS_MULTIPLE_FAULT_VIOLATION 0xC00002E8
const LONG ntStatus[] = {STATUS_WAIT_0,
STATUS_WAIT_1,
STATUS_WAIT_2,
STATUS_WAIT_3,
STATUS_WAIT_63,
STATUS_ABANDONED_WAIT_0,
STATUS_ABANDONED_WAIT_63,
STATUS_USER_APC,
STATUS_ALREADY_COMPLETE,
STATUS_TIMEOUT,
STATUS_PENDING,
DBG_EXCEPTION_HANDLED,
DBG_CONTINUE,
STATUS_OBJECT_NAME_EXISTS,
STATUS_THREAD_WAS_SUSPENDED,
STATUS_SEGMENT_NOTIFICATION,
STATUS_ALREADY_WIN32,
0x80000000,
STATUS_GUARD_PAGE_VIOLATION,
STATUS_DATATYPE_MISALIGNMENT,
STATUS_BREAKPOINT,
STATUS_SINGLE_STEP,
STATUS_BUFFER_OVERFLOW,
STATUS_NO_MORE_FILES,
DBG_EXCEPTION_NOT_HANDLED,
0xC0000000,
STATUS_UNSUCCESSFUL,
STATUS_NOT_IMPLEMENTED,
STATUS_INVALID_INFO_CLASS,
STATUS_ACCESS_VIOLATION,
STATUS_IN_PAGE_ERROR,
STATUS_INVALID_HANDLE,
STATUS_INVALID_PARAMETER,
STATUS_DLL_NOT_FOUND,
STATUS_ORDINAL_NOT_FOUND,
STATUS_ENTRYPOINT_NOT_FOUND,
STATUS_CONTROL_C_EXIT,
STATUS_DLL_INIT_FAILED,
STATUS_UNHANDLED_EXCEPTION,
STATUS_SYSTEM_PROCESS_TERMINATED,
STATUS_MULTIPLE_FAULT_VIOLATION,
STATUS_STACK_BUFFER_OVERRUN,
STATUS_INVALID_CRUNTIME_PARAMETER,
STATUS_ASSERTION_FAILURE,
STATUS_SXS_EARLY_DEACTIVATION,
STATUS_SXS_INVALID_DEACTIVATION};
__declspec(safebuffers)
BOOL CDECL PrintConsole(HANDLE hConsole, [SA_FormatString(Style="printf")] LPCWSTR lpFormat, ...)
{
WCHAR szOutput[1024];
DWORD dwOutput;
DWORD dwConsole;
va_list vaInput;
va_start(vaInput, lpFormat);
dwOutput = wvsprintf(szOutput, lpFormat, vaInput);
va_end(vaInput);
if (dwOutput == 0)
return FALSE;
if (!WriteConsole(hConsole, szOutput, dwOutput, &dwConsole, NULL))
return FALSE;
return dwConsole == dwOutput;
}
__declspec(noreturn)
VOID CDECL wmainCRTStartup(VOID)
{
OVERLAPPED overlapped = {STATUS_PENDING /*, 0, {0, 0}, (HANDLE) NULL */};
DWORD dwBytes;
DWORD dwError = ERROR_SUCCESS;
DWORD dwIndex = 0;
HANDLE hConsole = GetStdHandle(STD_ERROR_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE)
dwError = GetLastError();
else
{
// overlapped.Internal = STATUS_PENDING;
#ifndef QUIRKS
if (!GetOverlappedResult((HANDLE) NULL, &overlapped, &dwBytes, TRUE))
#elif QUIRKS == 1
if (!GetOverlappedResult(INVALID_HANDLE_VALUE, &overlapped, &dwBytes, TRUE))
#elif QUIRKS == 2
overlapped.hEvent = INVALID_HANDLE_VALUE;
if (!GetOverlappedResult((HANDLE) NULL, &overlapped, &dwBytes, TRUE))
#elif QUIRKS == 3
overlapped.hEvent = INVALID_HANDLE_VALUE;
if (!GetOverlappedResult(INVALID_HANDLE_VALUE, &overlapped, &dwBytes, TRUE))
#endif // QUIRKS
PrintConsole(hConsole,
L"GetOverlappedResult() returned error %lu\n",
dwError = GetLastError());
do
{
overlapped.Internal = ntStatus[dwIndex];
if (!GetOverlappedResult(INVALID_HANDLE_VALUE, &overlapped, &dwBytes, FALSE))
PrintConsole(hConsole,
L"GetOverlappedResult() returned error %lu for NTSTATUS 0x%08lX\n",
GetLastError(), overlapped.Internal);
else
PrintConsole(hConsole,
L"GetOverlappedResult() succeeded for NTSTATUS 0x%08lX\n",
overlapped.Internal);
}
while (++dwIndex < sizeof(ntStatus) / sizeof(*ntStatus));
if (!CloseHandle(hConsole))
PrintConsole(hConsole,
L"CloseHandle() returned error %lu\n",
GetLastError());
}
ExitProcess(dwError);
}
Build the console application quirk46.exe
from the
source file quirk46.c
created in step 1.:
SET CL=/GAFy /Oisy /W4 /Zl SET LINK=/ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE CL.EXE quirk46.c kernel32.lib user32.libFor details and reference see the MSDN articles Compiler Options and Linker Options.
Note: if necessary, see the MSDN article Use the Microsoft C++ toolset from the command line for an introduction.
Note: quirk46.exe
is a pure
Win32 console application and builds without the
MSVCRT
libraries.
Note: the command lines can be copied and pasted as block into a Command Processor window.
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk46.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk46.exe quirk46.obj kernel32.lib user32.lib
Execute the console application quirk46.exe
built in
step 2. to demonstrate the (mis)behaviour:
.\quirk46.exe CERTUTIL.EXE /ERROR %ERRORLEVEL%
GetOverlappedResult() returned error 6 GetOverlappedResult() succeeded for NTSTATUS 0x00000000 GetOverlappedResult() succeeded for NTSTATUS 0x00000001 GetOverlappedResult() succeeded for NTSTATUS 0x00000002 GetOverlappedResult() succeeded for NTSTATUS 0x00000003 GetOverlappedResult() succeeded for NTSTATUS 0x0000003F GetOverlappedResult() succeeded for NTSTATUS 0x00000080 GetOverlappedResult() succeeded for NTSTATUS 0x000000BF GetOverlappedResult() succeeded for NTSTATUS 0x000000C0 GetOverlappedResult() succeeded for NTSTATUS 0x000000FF GetOverlappedResult() succeeded for NTSTATUS 0x00000102 GetOverlappedResult() returned error 996 for NTSTATUS 0x00000103 GetOverlappedResult() succeeded for NTSTATUS 0x00010001 GetOverlappedResult() succeeded for NTSTATUS 0x00010002 GetOverlappedResult() succeeded for NTSTATUS 0x40000000 GetOverlappedResult() succeeded for NTSTATUS 0x40000001 GetOverlappedResult() succeeded for NTSTATUS 0x40000005 GetOverlappedResult() succeeded for NTSTATUS 0x4000001B GetOverlappedResult() returned error 317 for NTSTATUS 0x80000000 GetOverlappedResult() returned error 2147483649 for NTSTATUS 0x80000001 GetOverlappedResult() returned error 998 for NTSTATUS 0x80000002 GetOverlappedResult() returned error 2147483651 for NTSTATUS 0x80000003 GetOverlappedResult() returned error 2147483652 for NTSTATUS 0x80000004 GetOverlappedResult() returned error 234 for NTSTATUS 0x80000005 GetOverlappedResult() returned error 18 for NTSTATUS 0x80000006 GetOverlappedResult() returned error 688 for NTSTATUS 0x80010001 GetOverlappedResult() returned error 317 for NTSTATUS 0xC0000000 GetOverlappedResult() returned error 31 for NTSTATUS 0xC0000001 GetOverlappedResult() returned error 1 for NTSTATUS 0xC0000002 GetOverlappedResult() returned error 87 for NTSTATUS 0xC0000003 GetOverlappedResult() returned error 998 for NTSTATUS 0xC0000005 GetOverlappedResult() returned error 999 for NTSTATUS 0xC0000006 GetOverlappedResult() returned error 6 for NTSTATUS 0xC0000008 GetOverlappedResult() returned error 87 for NTSTATUS 0xC000000D GetOverlappedResult() returned error 126 for NTSTATUS 0xC0000135 GetOverlappedResult() returned error 182 for NTSTATUS 0xC0000138 GetOverlappedResult() returned error 127 for NTSTATUS 0xC0000139 GetOverlappedResult() returned error 572 for NTSTATUS 0xC000013A GetOverlappedResult() returned error 1114 for NTSTATUS 0xC0000142 GetOverlappedResult() returned error 574 for NTSTATUS 0xC0000144 GetOverlappedResult() returned error 591 for NTSTATUS 0xC000021A GetOverlappedResult() returned error 640 for NTSTATUS 0xC00002E8 GetOverlappedResult() returned error 1282 for NTSTATUS 0xC0000409 GetOverlappedResult() returned error 1288 for NTSTATUS 0xC0000417 GetOverlappedResult() returned error 668 for NTSTATUS 0xC0000420 GetOverlappedResult() returned error 14084 for NTSTATUS 0xC015000F GetOverlappedResult() returned error 14085 for NTSTATUS 0xC0150010 0x6 (WIN32: 6 ERROR_INVALID_HANDLE) -- 6 (6) Error message text: The handle is invalid. CertUtil: -error command completed successfully.Note: the
GetOverlappedResult()
function fails with Win32 error code 6 alias
ERROR_INVALID_HANDLE
if its hFile
argument is NULL
, its
bWait
argument is TRUE
, the
Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
STATUS_PENDING
and the hEvent
member is
NULL
!
Note: the hFile
argument of the
GetOverlappedResult()
function can be INVALID_HANDLE_VALUE
or
NULL
if the bWait
argument is
FALSE
or the Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
not STATUS_PENDING
!
OUCH: contrary to its documentation cited above,
the
GetOverlappedResult()
function succeeds if the Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
(for example) 0x00000001 alias STATUS_WAIT_1
,
0x00000002 alias STATUS_WAIT_2
,
0x00000003 alias STATUS_WAIT_3
,
0x0000003F alias STATUS_WAIT_63
,
0x00000080 alias STATUS_ABANDONED_WAIT_0
,
0x000000BF alias STATUS_ABANDONED_WAIT_63
,
0x000000C0 alias STATUS_USER_APC
,
0x00010001 alias DBG_EXCEPTION_HANDLED
,
0x00010002 alias DBG_CONTINUE
,
0x40000000 alias STATUS_OBJECT_NAME_EXISTS
,
0x40000001 alias STATUS_THREAD_WAS_SUSPENDED
or
0x40000005 alias STATUS_SEGMENT_NOTIFICATION
–
these 12
NTSTATUS
codes correspond to the Win32 error codes 731 alias
ERROR_WAIT_1
,
732 alias
ERROR_WAIT_2
,
733 alias
ERROR_WAIT_3
,
734 alias
ERROR_WAIT_63
,
735 alias
ERROR_ABANDONED_WAIT_0
,
736 alias
ERROR_ABANDONED_WAIT_63
,
737 alias
ERROR_USER_APC
,
258 alias
WAIT_TIMEOUT
,
766 alias
ERROR_DBG_EXCEPTION_HANDLED
,
767 alias
ERROR_DBG_CONTINUE
,
698 alias
ERROR_OBJECT_NAME_EXISTS
,
699 alias
ERROR_THREAD_WAS_SUSPENDED
respectively 702 alias
ERROR_SEGMENT_NOTIFICATION
!
Build the console application quirk46.exe
a second
time from the source file quirk46.c
created in
step 1., now with the preprocessor macro QUIRKS
defined as 1:
CL.EXE /DQUIRKS=1 quirk46.c kernel32.lib user32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk46.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk46.exe quirk46.obj kernel32.lib user32.lib
Execute the console application quirk46.exe
built in
step 4. to demonstrate the (mis)behaviour:
.\quirk46.exe
OUCH: if its
hFile
argument is
INVALID_HANDLE_VALUE
, its bWait
argument
is TRUE
and the Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
STATUS_PENDING
, the
GetOverlappedResult()
function waits infinitely instead to fail with Win32
error code 6 alias
ERROR_INVALID_HANDLE
or Win32 error code 87 alias
ERROR_INVALID_PARAMETER
!
Build the console application quirk46.exe
a third
time from the source file quirk46.c
created in
step 1., now with the preprocessor macro QUIRKS
defined as 2:
CL.EXE /DQUIRKS=2 quirk46.c kernel32.lib user32.lib
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. quirk46.c Microsoft (R) Incremental Linker Version 10.00.40219.386 Copyright (C) Microsoft Corporation. All rights reserved. /ENTRY:wmainCRTStartup /NODEFAULTLIB /SUBSYSTEM:CONSOLE /out:quirk46.exe quirk46.obj kernel32.lib user32.lib
Execute the console application quirk46.exe
built in
step 6. to demonstrate the (mis)behaviour:
.\quirk46.exe
OUCH: if its
hFile
argument is
NULL
, its bWait
argument is
TRUE
, the Internal
member of the
OVERLAPPED
structure pointed to by the lpOverlapped
argument is
STATUS_PENDING
and the hEvent
member is
INVALID_HANDLE_VALUE
, the
GetOverlappedResult()
function waits infinitely instead to fail with Win32
error code 6 alias
ERROR_INVALID_HANDLE
or Win32 error code 87 alias
ERROR_INVALID_PARAMETER
!
GetOverlappedResultEx()
function is left as an exercise to the reader!
SYMPTOMSThe MSDN article Registry Redirector states:When a 32-bit application is writing the %ProgramFiles% registry value on a computer that is running a 64-bit version of Windows Vista, Windows Vista automatically changes this string to %ProgramFiles(x86)%. This behavior cannot be changed.
This behavior also occurs in the 64-bit versions of Windows Server 2003 and of Windows XP.
CAUSE
This behavior occurs because %ProgramFiles% is a keyword for translation from a 64-bit operation to a 32-bit operation. This behavior enables a 32-bit application to work correctly with the %ProgramFiles% registry value when the application reads the %ProgramFiles% registry value later.
To help 32-bit applications that write REG_SZ or REG_EXPAND_SZ data containing %ProgramFiles% or %commonprogramfiles% to the registry, WOW64 intercepts these write operations and replaces them with "%ProgramFiles(x86)%" and "%commonprogramfiles(x86)%". For example, if the Program Files directory is on the C drive, then "%ProgramFiles(x86)%" expands to "C:\Program Files (x86)". The replacement occurs only if the following conditions are met:WOW64 Implementation Details File System RedirectorWindows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: The KEY_WOW_64_64KEY flag does not affect whether a key is replaced. This flag affects replacement starting with Windows 7 and Windows Server 2008 R2.
- The string must begin with %ProgramFiles% or %commonprogramfiles%. If the string begins with a space or any character other than %, it is not replaced.
- The case of %ProgramFiles% or %commonprogramfiles% must be exactly as shown because the string comparison is case-sensitive. For example, if the string begins with %CommonProgramFiles% instead of %commonprogramfiles%, it is not replaced.
- The string cannot exceed MAX_PATH*2+15 characters. If it exceeds this length, it is not replaced.
- The key cannot be opened with KEY_WOW64_64KEY. This flag specifies that operations on the key should be performed on the 64-bit registry view, so it is not replaced. For more information, see Accessing an Alternate Registry View.
In addition, REG_SZ or REG_EXPAND_SZ keys containing system32 are replaced with syswow64. The string must begin with the path pointing to or under %windir%\system32. The string comparison is not case-sensitive. Environment variables are expanded before matching the path, so all of the following paths are replaced: %windir%\system32, %SystemRoot%\system32, and C:\windows\system32. This patch is applied only to the keys that were reflected prior to Windows 7.
For more information, see the following topics:
Start the 32-bit command processor
%SystemRoot%\SysWoW64\Cmd.exe
and verify the documented behaviour:
REG.EXE ADD HKEY_CURRENT_USER /VE /F /D %ProgramFiles^% REG.EXE QUERY HKEY_CURRENT_USER /VE REG.EXE DELETE HKEY_CURRENT_USER /VE /FNote: the command lines can be copied and pasted as block into a Command Processor window.
The operation completed successfully. HKEY_CURRENT_USER (Default) REG_SZ %ProgramFiles(x86)% The operation completed successfully.
Repeat the operations from step 1. with
\Common Files
appended to the unexpanded environment
variable string %ProgramFiles%
:
REG.EXE ADD HKEY_CURRENT_USER /VE /F /D %ProgramFiles^%\Common" "Files REG.EXE QUERY HKEY_CURRENT_USER /VE REG.EXE DELETE HKEY_CURRENT_USER /VE /F
The operation completed successfully. HKEY_CURRENT_USER (Default) REG_SZ %ProgramFiles(x86)%\Common Files The operation completed successfully.
Repeat the operations from step 3. using the unexpanded
environment variable string %CommonProgramFiles%
instead of %ProgramFiles%
:
REG.EXE ADD HKEY_CURRENT_USER /VE /F /D %CommonProgramFiles^% REG.EXE QUERY HKEY_CURRENT_USER /VE REG.EXE DELETE HKEY_CURRENT_USER /VE /F
The operation completed successfully. HKEY_CURRENT_USER (Default) REG_SZ %CommonProgramFiles% The operation completed successfully.OUCH: despite the striking similarity to the %ProgramFiles% and %ProgramFiles(x86)% pair, %CommonProgramFiles% is not translated to %CommonProgramFiles(x86)%, i.e. it is most obviously not
a keyword for translation from a 64-bit operation to a 32-bit operation.–
Honi soit qui mal y pense
!
Repeat the operations from step 1. using the unexpanded
environment variable string %commonprogramfiles%
instead of %ProgramFiles%
:
REG.EXE ADD HKEY_CURRENT_USER /VE /F /D %commonprogramfiles^% REG.EXE QUERY HKEY_CURRENT_USER /VE REG.EXE DELETE HKEY_CURRENT_USER /VE /F
The operation completed successfully. HKEY_CURRENT_USER (Default) REG_SZ %commonprogramfiles(x86)% The operation completed successfully.
%SystemRoot%\SysWoW64\RegEdit.exe
,
either interactive or with a .reg
script, the
SetupAPI with a
.inf
script via one of the command lines
"%SystemRoot%\SysWoW64\RunDLL32.exe" "%SystemRoot%\SysWoW64\SetupAPI.dll",InstallHinfSection ‹pathname.inf›
or
"%SystemRoot%\SysWoW64\RunDLL32.exe" "%SystemRoot%\SysWoW64\AdvPack.dll",LauchINFSection ‹pathname.inf›
,
as well as the Windows Script Host with a
JScript
or
VBScript
via one of the command lines
"%SystemRoot%\SysWoW64\CScript.exe" ‹pathname.js›
or
"%SystemRoot%\SysWoW64\WScript.exe" ‹pathname.vbs›
,
is left as an exercise to the reader!
REGEDIT4
; Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
[HKEY_CURRENT_USER]
"1"="%ProgramFiles%"
"2"="%ProgramFiles%\\Common Files"
"3"="%CommonProgramFiles%"
"4"="%commonprogramfiles%"
; Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
[Version]
DriverVer = 04/27/2004, 0.8.1.5
Provider = "Stefan Kanthak"
Signature = "$Windows NT$"
[DefaultInstall.NTx86]
AddReg = AddReg
[AddReg]
HKCU,,"1",0,"%%ProgramFiles%%"
HKCU,,"2",0,"%%ProgramFiles%%\Common Files"
HKCU,,"3",0,"%%CommonProgramFiles%%"
HKCU,,"4",0,"%%commonprogramfiles%%"
Rem Copyleft © 2004-2025, Stefan Kanthak <stefan.kanthak@nexgo.de>
With WScript.CreateObject("WScript.Shell")
.RegWrite "HKEY_CURRENT_USER\1", "%ProgramFiles%"
.RegWrite "HKEY_CURRENT_USER\2", "%ProgramFiles%\Common Files"
.RegWrite "HKEY_CURRENT_USER\3", "%CommonProgramFiles%"
.RegWrite "HKEY_CURRENT_USER\4", "%commonprogramfiles%"
WScript.StdOut.WriteLine .RegRead("HKEY_CURRENT_USER\1")
WScript.StdOut.WriteLine .RegRead("HKEY_CURRENT_USER\2")
WScript.StdOut.WriteLine .RegRead("HKEY_CURRENT_USER\3")
WScript.StdOut.WriteLine .RegRead("HKEY_CURRENT_USER\4")
.RegDelete "HKEY_CURRENT_USER\1"
.RegDelete "HKEY_CURRENT_USER\2"
.RegDelete "HKEY_CURRENT_USER\3"
.RegDelete "HKEY_CURRENT_USER\4"
End With
WshShell Object
RegDelete Method
RegRead Method
RegWrite Method
Gatesparticle with a mass equivalent to some 100 G$.
Use the X.509 certificate to send S/MIME encrypted mail.
Note: email in weird format and without a proper sender name is likely to be discarded!
I dislike
HTML (and even
weirder formats too) in email, I prefer to receive plain text.
I also expect to see your full (real) name as sender, not your
nickname.
I abhor top posts and expect inline quotes in replies.
as iswithout any warranty, neither express nor implied.
cookiesin the web browser.
The web service is operated and provided by
Telekom Deutschland GmbH The web service provider stores a session cookie
in the web
browser and records every visit of this web site with the following
data in an access log on their server(s):