Decoding Windows Error Codes — The Three-Layer Structure of Win32 Errors, HRESULT, and NTSTATUS
· Go Komura · Windows, Error Codes, HRESULT, NTSTATUS, Win32 API, Troubleshooting, Debugging, Windows Development
“The app screen showed error 0x80004005. What does it mean?” — In incident-investigation consultations, this kind of question is a classic. Anyone who has pasted the number from an error dialog straight into a search engine and been met with a flood of unrelated articles — a Windows Update failure, a shared folder that will not connect, a VBA runtime error, a database connection failure — and become more confused has plenty of company.
That happens because 0x80004005 (E_FAIL) is a generic code whose only meaning is “unspecified failure”. The same code is used in countless situations, so searching on the code alone will not reach the cause. On the other hand, a code such as 0x80070005 can, if you know the structure, be decomposed in a few seconds before you search into “Win32 error number 5 = access denied, wrapped as an HRESULT”.
Windows error codes, for historical reasons, form three layers — Win32 error codes, HRESULT, and NTSTATUS — and they are converted across layers. Once you have that structure in your head, you can judge for yourself “which layer whose party returned this code” and “what the essential code is”, and the opening move of an investigation becomes much faster.
Aimed at IT staff in small and medium businesses and at Windows app developers, this article organizes how to tell the three error-code systems apart and how to decompose them, the relationship to .NET exceptions, and practical lookup with err.exe and PowerShell — grounded in Microsoft Learn and the published specification [MS-ERREF] as of August 2026.
1. The Bottom Line First
- Windows error codes are mainly three systems. Win32 error codes (the small decimal
GetLastErrorreturns), HRESULT (the 32-bit code from COM onward, hex starting with 0x8 or a negative decimal), and NTSTATUS (kernel-layer codes; errors start with 0xC).123 - Decimal and hexadecimal are different notations for the same code. “Error 5”, “0x5”, and “the low 16 bits of 0x80070005” all refer to ERROR_ACCESS_DENIED (access denied).1
- 0x8007xxxx is “a wrapped Win32 error”. It is a Win32 error code stored in HRESULT FACILITY_WIN32 (7); convert the low 16 bits to decimal and you have the essential code. This is the most important pattern in reading error codes.45
- 0x80004005 (E_FAIL) is not a cause code. It means “Unspecified failure” and holds no more information. Rather than digging into this code, you should look for the originating context and accompanying logs.6
- A negative decimal (-2147467259 and the like) is an HRESULT. The most significant bit of the 32 bits (the failure bit) is set, so a signed display is negative. Convert it to hex and then read it.2
- An 8-digit value starting with 0xC is an NTSTATUS. 0xC0000005 (access violation) and 0xC0000135 (DLL not found) show up constantly in the event log and dumps at crash time. They are unrelated to Win32 error number 5.7
- The same code changes meaning with context. The cause of error 5 ranges over ACL, elevation, antivirus, a held file, and more, and the “file not found” of error 2 is often a dependent DLL. Always read the meaning of the code together with which API failed against what.1
- Conversion and lookup tools are standard.
certutil -errorandnet helpmsgare built into Windows; PowerShell’sWin32Exceptiongets the message; on a development machine err.exe (Microsoft Error Lookup Tool); in dump analysis WinDbg’s!error.8910 - In .NET, an HRESULT is mapped to an exception type. A known HRESULT goes to the corresponding exception type (E_ACCESSDENIED → UnauthorizedAccessException, and the like); an unknown one becomes a COMException; the original value remains in
Exception.HResult.11
In one sentence, the pattern of a Windows error-code investigation is “align the notation to hex → judge which layer the code is → decompose and take out the essential code → read it together with context”.
2. Windows Has Three Error-Code Systems
First, the overall map. Windows error codes split mainly into the following three systems, by the layer that returns them.
| System | Main party that returns it | Typical appearance | Representative example |
|---|---|---|---|
| Win32 error code | Win32 API (GetLastError), a command’s exit code |
A small decimal (0–15999) | 5 = ERROR_ACCESS_DENIED |
| HRESULT | A COM component, the shell, an installer, many frameworks | 8-digit hex starting with 0x8, or a negative decimal | 0x80004005 = E_FAIL |
| NTSTATUS | The kernel, a driver, a native API (ntdll) | Errors are 8-digit hex starting with 0xC | 0xC0000005 = STATUS_ACCESS_VIOLATION |
Historically they stacked in this order: Win32 error codes that inherited MS-DOS error numbers, NTSTATUS that the NT kernel uses internally, and HRESULT designed at the introduction of COM to “pack success/failure and the origin into 32 bits”. On current Windows, a conversion flow is everyday: the kernel returns an NTSTATUS, the Win32 subsystem converts it to a Win32 error code, and the COM layer wraps it further as an HRESULT.124
flowchart TB
accTitle: Conversion flow across the three systems
accDescr: The Win32 subsystem converts an NTSTATUS the kernel returned into a Win32 error code, and the COM layer wraps it further as an HRESULT
kernel["Kernel and drivers"] --> nt["NTSTATUS (errors are 0xC…)"]
nt -->|The Win32 subsystem converts| win["Win32 error code (5 and the like)"]
win -->|The COM layer wraps| hr["HRESULT (0x8007xxxx)"]
Figure 1: The conversion flow across layers. A kernel NTSTATUS becomes a Win32 error, and is wrapped further as an HRESULT.
2.1. Get used to reading decimal and hexadecimal interchangeably
Before you tell the three systems apart, you need to absorb notation wobble. The same code is displayed as decimal or hex depending on the situation.
- “Error 5”, “error code: 0x5” → the same ERROR_ACCESS_DENIED
- “Error 1223”, “0x4C1” → the same ERROR_CANCELLED
- “0x80070005”, “-2147024891” → the same HRESULT
In PowerShell the conversion is one line.
# Decimal → hex
'0x{0:X8}' -f 1223 # 0x000004C1
'0x{0:X8}' -f -2147024891 # 0x80070005 (negative = HRESULT to hex)
# Hex → decimal
0x4C1 # 1223
When you see a negative decimal that starts with “-214…”, reflexively convert it to hex. That alone cuts a lot of getting lost at the entrance of an investigation.
flowchart TB
accTitle: Three appearances of the same code
accDescr: Decimal error 5, hex 0x5, and the low 16 bits of 0x80070005 all refer to the same ERROR_ACCESS_DENIED
d["Decimal notation: error 5"] --> same["ERROR_ACCESS_DENIED"]
h["Hex notation: 0x5"] --> same
l["The low 16 bits of 0x80070005"] --> same
same -.-> memo["Different notation, the same code"]
Figure 2: Decimal, hex, and the low 16 bits of an HRESULT are only different notations for the same code.
3. Win32 Error Codes — GetLastError and FORMAT_MESSAGE
3.1. Basic GetLastError behavior
Many Win32 APIs such as CreateFile and RegOpenKeyEx indicate failure with the return value (FALSE, NULL, INVALID_HANDLE_VALUE, and the like), and store the detailed error code in a “last-error code” held per thread. The caller retrieves it with GetLastError immediately after confirming failure.13
There are two practical caveats.13
- Read it immediately after failure. If you insert another API call (a logging function, for example) in between, that call can overwrite the last-error code.
- Do not rely on the value on success. Some APIs clear the last-error code to 0 on success; some do not touch it. The rule is to confirm failure from the return value and then read.
sequenceDiagram
accTitle: Read GetLastError immediately after failure
accDescr: After confirming failure from the return value, retrieve the last-error code with GetLastError immediately, without inserting another API call
participant app as App
participant api as Win32 API
app->>api: CreateFile call
api-->>app: Failure return value
app->>api: GetLastError
api-->>app: Code 5
Note over app: Inserting another API in between can overwrite it
Figure 3: Read the last-error code immediately after failure. Inserting another API call in between can overwrite it.
To get a message string from a code, use FormatMessage with the FORMAT_MESSAGE_FROM_SYSTEM flag.1
#include <windows.h>
#include <stdio.h>
void PrintLastError(const wchar_t* apiName)
{
DWORD code = GetLastError(); // Call immediately after failure (do not insert another API)
wchar_t message[512] = L"";
FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, code, 0, message, 512, nullptr);
wprintf(L"%s failed: %lu (0x%08lX) %s", apiName, code, code, message);
}
Leaving both decimal and hex and the message text in your own app’s log, like this, makes a later investigation one step faster.
flowchart TB
accTitle: Look up a message from a code and leave it in the log
accDescr: Specify the FORMAT_MESSAGE_FROM_SYSTEM flag to FormatMessage to get the error code's message string, and leave decimal, hex, and the message text in the log
code["Error code (example: 5)"] --> fm["Get the string with FormatMessage"]
fm --> msg["Message text"]
msg --> log["Record in the log"]
log -.-> both["Write decimal, hex, and the text together"]
Figure 4: Convert an error code to a message string with FormatMessage, and leave decimal, hex, and the text together in the log.
3.2. Representative codes that come up constantly in the field
Win32 error codes are defined in the range 0–15999, and Microsoft Learn has a full list.1 Among them, the faces you meet over and over in incident investigation are the following.
| Decimal | Hex | Symbol | Meaning |
|---|---|---|---|
| 2 | 0x2 | ERROR_FILE_NOT_FOUND | The specified file was not found |
| 3 | 0x3 | ERROR_PATH_NOT_FOUND | The specified path was not found |
| 5 | 0x5 | ERROR_ACCESS_DENIED | Access was denied |
| 32 | 0x20 | ERROR_SHARING_VIOLATION | Another process is using it and access is not possible |
| 87 | 0x57 | ERROR_INVALID_PARAMETER | The parameter is incorrect |
| 122 | 0x7A | ERROR_INSUFFICIENT_BUFFER | The buffer passed is too small |
| 998 | 0x3E6 | ERROR_NOACCESS | Invalid access to a memory location |
| 1223 | 0x4C1 | ERROR_CANCELLED | The operation was cancelled by the user |
Of these, 998 (ERROR_NOACCESS) is not “access denied” but the Win32 expression of a memory access violation, the shape of the NTSTATUS STATUS_ACCESS_VIOLATION discussed later after conversion to the Win32 layer. Watch the confusion with number 5. Also, 1223 (ERROR_CANCELLED) is a code that appears when the user chooses “No” on a UAC elevation dialog, for example — more “it was cancelled” than an error.
flowchart TB
accTitle: Error 998 and 5 are different things
accDescr: 998 is a memory access violation, an NTSTATUS access violation converted to the Win32 layer, and differs in meaning from 5, which represents access denied
nt["NTSTATUS 0xC0000005"] -->|Converted to the Win32 layer| e998["Error 998 (ERROR_NOACCESS)"]
e998 -.-> m1["Meaning is a memory access violation"]
e5["Error 5 (access denied)"] -.-> m2["A permissions problem. A different thing from 998"]
Figure 5: Error 998 is an NTSTATUS access violation converted to the Win32 layer, a different thing from access-denied 5.
3.3. The same code changes meaning with context
More important than memorizing the table of representative codes is the sense that an error code only tells you “the kind of failure”.
- Error 5 (access denied): Candidates for the cause range widely — insufficient NTFS ACL, writing a protected area without administrator privileges, a block by antivirus or AppLocker, insufficient privileges on a service account, and so on.
- Error 2 (file not found): It is not necessarily the file the user specified. A dependent DLL the EXE tried to load implicitly, a settings file seen in the wrong place because of registry redirection (32-bit/64-bit), a path whose environment-variable expansion failed — “which file” was not found is not visible from the code.
- Error 32 (sharing violation): “Which process is holding it” is the real question, but the code does not tell you that.
flowchart TB
accTitle: The cause of error 5 is decided by context
accDescr: Even the same access denied has several candidate causes such as insufficient ACL or missing administrator privileges, and you need to identify which API failed against what
e5["Error 5(access denied)"] --> q{"Which candidate?"}
q --> acl{"ACL or admin?"}
q --> other{"Product or service?"}
acl --> c1["Insufficient ACL"]
acl --> c2["No administrator"]
other --> c3["Security-product block"]
other --> c4["Service privileges low"]
c1 --> next["Procmon: failed target"]
c2 --> next
c3 --> next
c4 --> next
Figure 6: A code only tells you “the kind of failure”. Error 5 has several candidate causes, and identifying the target is required.
The tool that measures “which API, against which object name, returned which result” is Process Monitor. How to use it is covered in detail in “A Practical Guide to Process Monitor (ProcMon)”. Looking up the meaning of an error code and identifying the target that failed are two wheels of the same cart.
4. HRESULT — Reading the Structure Packed into 32 Bits
4.1. Bit layout
HRESULT is a format that packs success/failure, origin, and a detail code into a single 32-bit value. The published specification [MS-ERREF] defines it with the following layout.2
| Bit position | Name | Meaning |
|---|---|---|
| 31 | S | Severity. 0 = success, 1 = failure |
| 30 | R | Reserved (part of severity when mapping NTSTATUS) |
| 29 | C | Customer bit. 1 means a code defined by someone other than Microsoft |
| 28 | N | 1 means an NTSTATUS value mapped into HRESULT space |
| 27 | X | Reserved (0) |
| 26–16 | Facility | A facility code that indicates the origin (11 bits) |
| 15–0 | Code | A detail code within the facility (16 bits) |
The most significant S bit is 1, that is, an HRESULT whose hex notation starts at 0x8 or above is a failure. Displaying that as a signed 32-bit integer makes it negative — that is the identity of the “-214…” mentioned earlier.
flowchart TB
accTitle: The relationship between the S bit and a negative display
accDescr: A failure HRESULT has the most significant S bit set to 1, so in hex it starts at 0x8 or above, and as a signed 32-bit integer it is negative
s["S bit = 1 (failure)"] --> hex["Hex starts at 0x8 or above"]
hex --> neg["A signed display is negative"]
neg --> back["When you see a negative, convert to hex and read"]
Figure 7: A failure HRESULT starts at 0x8 or above because the S bit is 1, and a signed display is negative.
Representative Facility values are as follows.5
| Facility | Value | Hex appearance | Meaning |
|---|---|---|---|
| FACILITY_NULL | 0 | 0x8000xxxx | Widely common codes (E_FAIL, E_UNEXPECTED, and the like) |
| FACILITY_RPC | 1 | 0x8001xxxx | RPC-origin |
| FACILITY_ITF | 4 | 0x8004xxxx | An interface-defined error (meaning depends on the interface) |
| FACILITY_WIN32 | 7 | 0x8007xxxx | A wrapped Win32 error code |
| FACILITY_WINDOWS | 8 | 0x8008xxxx | Additional Microsoft-defined interfaces |
4.2. Decomposing 0x80004005 and 0x80070005
Let us actually decompose them.
For 0x80004005: S=1 (failure), Facility=(0x80004005 » 16) & 0x7FF = 0 (FACILITY_NULL), Code=0x4005. A generic FACILITY_NULL code, defined as E_FAIL “Unspecified failure”.6 In other words this code holds only the meaning “a failure that cannot report detail”. When you see 0x80004005, stop digging into the code itself there, and shift the investigation’s weight to “which component returned it” and “is there detail in the event log or the app log at the same time”.
For 0x80070005: S=1, Facility=7 (FACILITY_WIN32), Code=0x0005=5. You can see that it is Win32 error number 5 (ERROR_ACCESS_DENIED) wrapped as an HRESULT. The alias E_ACCESSDENIED is, in substance, this value.6
Even for the same “access denied”, 0x80070005 is a wrap of a concrete failure that occurred at the Win32 layer, and the amount of information is completely different from 0x80004005.
flowchart TB
accTitle: Decomposition of 0x80004005 and 0x80070005
accDescr: 0x80004005 is the generic FACILITY_NULL code E_FAIL, holds no detail, and should move to a context investigation; 0x80070005 is FACILITY_WIN32 and can be seen as a wrap of Win32 error number 5, access denied
a["0x80004005"] --> af["Facility=0 (FACILITY_NULL)"]
af --> ac["Code=0x4005 → E_FAIL"]
ac --> ax["Unspecified failure. On to a context investigation"]
b["0x80070005"] --> bf["Facility=7 (FACILITY_WIN32)"]
bf --> bc["Code=0x0005 → 5"]
bc --> bx["ERROR_ACCESS_DENIED"]
Figure 8: Even the same “failure” has a different amount of information once decomposed. 0x80070005 can be walked to Win32 error number 5.
4.3. The most important pattern: 0x8007xxxx = HRESULT_FROM_WIN32
To convey a failure from a lower layer that can only return a Win32 error code to an upper layer that returns HRESULT (a COM method or the .NET runtime), winerror.h provides the HRESULT_FROM_WIN32 macro.4 The behavior is “store the Win32 error code in the low 16 bits, set Facility to FACILITY_WIN32 (7), and set the S bit to 1”.
flowchart TB
accTitle: How HRESULT_FROM_WIN32 works
accDescr: Store the Win32 error code in the low 16 bits, set Facility to 7 and the S bit to 1, and assemble a 0x8007xxxx HRESULT
win["Win32 error code (example: 5)"] --> low["Store in the low 16 bits"]
low --> fac["Set Facility to 7"]
fac --> sbit["Set the S bit to 1"]
sbit --> hr["0x80070005"]
Figure 9: HRESULT_FROM_WIN32 stores the Win32 error in the low 16 bits and sets Facility=7 and the S bit.
ERROR_ACCESS_DENIED (5) --HRESULT_FROM_WIN32--> 0x80070005
ERROR_SHARING_VIOLATION (32) --HRESULT_FROM_WIN32--> 0x80070020
ERROR_INVALID_PARAMETER (87) --HRESULT_FROM_WIN32--> 0x80070057 (= E_INVALIDARG)
ERROR_OUTOFMEMORY (14) --HRESULT_FROM_WIN32--> 0x8007000E (= E_OUTOFMEMORY)
To read the other way, take the low 16 bits in PowerShell.
0x80070005 -band 0xFFFF # 5 → ERROR_ACCESS_DENIED
0x80072EE7 -band 0xFFFF # 12007 → ERROR_INTERNET_NAME_NOT_RESOLVED (WinINet)
As in the second example, WinINet and WinHTTP errors (the 12000s) are also defined in Win32 error-code space1, so a networking 0x8007xxxx can be decomposed with the same procedure. Getting “when you see 0x8007, convert the low 4 digits to decimal” into muscle memory is the number-one practical skill this article would like you to take home.
There is the opposite caveat for 0x8004xxxx (FACILITY_ITF). A FACILITY_ITF code has a different party defining the meaning per interface, so the same 32-bit value can mean something different if the party that returned it is different.5 For an unfamiliar 0x8004xxxx, look it up not in a generic search but in the documentation of the component that returned it (a library, a driver SDK, a server product).
flowchart TB
accTitle: How you look it up changes between 0x8007 and 0x8004
accDescr: A FACILITY_WIN32 0x8007xxxx can be read by mechanically decomposing the low 16 bits, but a FACILITY_ITF 0x8004xxxx has a different party defining the meaning per interface, so look it up in the returning component's materials
hr{"Facility is?"} -->|7, WIN32| w["Convert the low 16 bits to decimal"]
hr -->|4, ITF| i["Meaning differs by the party that returned it"]
w --> ww["Read it as a Win32 error"]
i --> ii["Look it up in the returning party's materials"]
Figure 10: 0x8007xxxx can be decomposed mechanically; 0x8004xxxx is looked up in the returning component’s materials.
5. NTSTATUS — Kernel-Layer Codes and the World of Crashes
5.1. Layout and Severity
NTSTATUS is a 32-bit code used by the kernel, device drivers, and ntdll native APIs, and its layout is similar to HRESULT without being the same.3
| Bit position | Name | Meaning |
|---|---|---|
| 31–30 | Sev | Severity. 00 = success, 01 = informational, 10 = warning, 11 = error |
| 29 | C | Customer bit |
| 28 | N | Reserved (0, so that a map to HRESULT is possible) |
| 27–16 | Facility | Facility (12 bits) |
| 15–0 | Code | Detail code |
Because severity is 2 bits, you can read the kind from the leading hex digit. 0xC… is an error (11), 0x8… is a warning (10), 0x4… is informational (01), 0x0–0x3… is success. The breakpoint exception 0x80000003 (STATUS_BREAKPOINT) is a representative example of “a warning, not an error”.37
flowchart TB
accTitle: An NTSTATUS can be read by kind from the leading digit
accDescr: Because severity is 2 bits, an NTSTATUS can be read as error if the leading hex digit is 0xC, warning if 0x8, informational if 0x4, and success if 0x0 through 0x3
head{"The leading hex digit is?"} -->|0xC| e["Error"]
head -->|0x8| w["Warning"]
head -->|0x4| i["Informational"]
head -->|0x0–0x3| s["Success"]
w -.-> ex["Example: 0x80000003 is a warning"]
Figure 11: An NTSTATUS can be read by kind from the leading hex digit. 0x80000003 is “a warning, not an error”.
5.2. Where you meet it — exception codes, STOP codes, and the event log
The situations in which IT staff and developers meet NTSTATUS are mainly crash-related.
- An application-crash exception code: The “Exception code: 0xc0000005” recorded in the event log’s “Application Error (event ID 1000)” is an NTSTATUS. Representative values are as follows.7
| Value | Symbol | Meaning |
|---|---|---|
| 0xC0000005 | STATUS_ACCESS_VIOLATION | Access violation (an illegal memory access) |
| 0xC0000135 | STATUS_DLL_NOT_FOUND | A required DLL was not found and it cannot start |
| 0xC00000FD | STATUS_STACK_OVERFLOW | Stack overflow |
| 0xC0000374 | STATUS_HEAP_CORRUPTION | Heap corruption |
- A blue-screen STOP code: They look similar at a glance, but a STOP code (bug check code) is a numbering system of its own, separate from NTSTATUS, such as 0x0000009F (DRIVER_POWER_STATE_FAILURE), and has a dedicated reference.14 Remembering only the distinction “0xC0000005 is NTSTATUS; STOP 0x9F is a bug check code and you must not look it up in an NTSTATUS table” is enough.
- Process Monitor’s Result column: NAME NOT FOUND and ACCESS DENIED in Procmon’s Result column are display names of the NTSTATUS the kernel returned (STATUS_OBJECT_NAME_NOT_FOUND, STATUS_ACCESS_DENIED). It is also a place where you can feel the layer correspondence of observing a file-I/O failure in NTSTATUS vocabulary and that failure being converted to a Win32 error and arriving at the app.
flowchart TB
accTitle: Telling an exception code from a STOP code
accDescr: Read an event-log exception code as an NTSTATUS; look up a blue-screen STOP code in the dedicated bug-check-code reference, a different system
q{"Where did the code appear?"} -->|Exception code| nt["Read it as an NTSTATUS"]
q -->|STOP code| bc["Look it up in the bug-check-code table"]
nt -.-> n1["Example: 0xC0000005"]
bc -.-> b1["Example: 0x0000009F"]
Figure 12: An event-log exception code is an NTSTATUS; a blue-screen STOP code is a different system. Do not look them up in the wrong table.
Investigation beyond the exception code, that is, capturing and analyzing a crash dump, is covered in “An Introduction to Collecting Windows Crash Dumps” and “Reading Crash Dumps with WinDbg + SOS”.
5.3. The relationship to HRESULT — the N bit and RtlNtStatusToDosError
The bridge between NTSTATUS and the other two layers has two paths.
- A map into HRESULT space: Setting the HRESULT N bit (0x10000000) brings an NTSTATUS value into HRESULT space as-is (the HRESULT_FROM_NT macro in winerror.h). Mapping 0xC0000005 becomes 0xD0000005, for example. When you see an HRESULT that starts with 0xD, the correct procedure is to strip the N bit and read it as an NTSTATUS.2
- Conversion to a Win32 error code: ntdll’s
RtlNtStatusToDosErrorconverts an NTSTATUS to the corresponding Win32 error code. A value with no defined correspondence becomes ERROR_MR_MID_NOT_FOUND.12 For example STATUS_ACCESS_VIOLATION (0xC0000005) is converted to ERROR_NOACCESS (998), and STATUS_OBJECT_NAME_NOT_FOUND (0xC0000034) to ERROR_FILE_NOT_FOUND (2). It is also useful to remember that the kernel’s rich vocabulary is sometimes rounded to a coarser distinction at the Win32 layer.
flowchart TB
accTitle: Two bridges from NTSTATUS to the other layers
accDescr: An NTSTATUS is passed to the other layers in two ways: mapped into HRESULT space by setting the N bit, and converted to a Win32 error code by RtlNtStatusToDosError
nt["NTSTATUS (0xC0000005)"] -->|Set the N bit| hr["HRESULT (0xD0000005)"]
nt -->|RtlNtStatusToDosError| win["Win32 error 998 (ERROR_NOACCESS)"]
win -.-> memo["ERROR_MR_MID_NOT_FOUND if no correspondence is defined"]
Figure 13: There are two NTSTATUS bridges. A 0xD start is read as an NTSTATUS after stripping the N bit.
6. COM and .NET — How an Error Code Is Mapped to an Exception
6.1. The COM style — HRESULT + IErrorInfo
A COM method fundamentally returns HRESULT, but there is a limit to what can be packed into 32 bits, so as a supplement the IErrorInfo mechanism can convey an error description string and the origin separately. In C++, the compiler-supported _com_error class handles HRESULT and IErrorInfo together. An app whose error dialog shows “code + description” is often carrying the description through this mechanism.
flowchart TB
accTitle: IErrorInfo that supplements HRESULT
accDescr: There is a limit to what can be packed into a 32-bit HRESULT, so an error description string and the origin are conveyed separately with IErrorInfo, and in C++ the _com_error class handles both together
hr["HRESULT (32 bits only)"] --> lim["There is a limit to what can be packed"]
lim --> ei["IErrorInfo carries the description"]
ei --> ce["_com_error handles them together"]
ce -.-> dlg["The dialog's code + description"]
Figure 14: A description string that does not fit in a 32-bit HRESULT is carried separately by IErrorInfo.
6.2. The .NET style — from HRESULT to an exception type
When the .NET runtime receives an HRESULT failure in COM interop, it converts it to an exception. A known HRESULT is mapped to the corresponding exception type; an unknown one becomes a COMException.11
flowchart TB
accTitle: Mapping from HRESULT to a .NET exception
accDescr: A failure HRESULT received in COM interop is converted to the corresponding exception type if known, or to COMException if unknown, and in either case the original value is kept in Exception.HResult
hr["Failure HRESULT"] --> known{"A known mapping?"}
known -->|Yes| typed["Convert to the corresponding exception type"]
known -->|No| comex["Convert to COMException"]
typed --> keep["The original value is kept in Exception.HResult"]
comex --> keep
Figure 15: .NET maps an HRESULT to an exception type, and the original value remains in Exception.HResult on every exception.
| HRESULT | .NET exception type |
|---|---|
| E_ACCESSDENIED (0x80070005) | UnauthorizedAccessException |
| E_OUTOFMEMORY (0x8007000E) | OutOfMemoryException |
| E_INVALIDARG (0x80070057) | ArgumentException |
| E_NOTIMPL (0x80004001) | NotImplementedException |
| A value with no mapping defined | COMException (the original value in the ErrorCode property) |
On every exception, the original HRESULT is kept in the Exception.HResult property. A branch in file-I/O exception handling such as “retry only on a sharing violation” can be written with this value.
try
{
using var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
}
catch (IOException ex) when (ex.HResult == unchecked((int)0x80070020))
{
// 0x80070020 = HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION)
// Another process is holding the file — wait a little and retry, for example
}
6.3. P/Invoke and GetLastError
When you call a Win32 API directly via P/Invoke, specify SetLastError = true on DllImport (or LibraryImport), then retrieve with Marshal.GetLastWin32Error (from .NET 6, the equivalent GetLastPInvokeError). Defining GetLastError itself as a P/Invoke and calling it is inaccurate, because an API call inside the runtime can overwrite the value.15
flowchart TB
accTitle: Retrieving the last error in P/Invoke
accDescr: Specifying SetLastError as true and retrieving with Marshal.GetLastWin32Error is correct; P/Invoking GetLastError directly is inaccurate because of a runtime overwrite
pi["Call a Win32 API via P/Invoke"] --> ok["Specify SetLastError=true"]
ok --> get["Retrieve with GetLastWin32Error"]
pi --> ng["A definition that calls GetLastError directly"]
ng --> bad["The runtime overwrites it and it is inaccurate"]
Figure 16: In P/Invoke, use SetLastError=true and Marshal.GetLastWin32Error as a set. Calling GetLastError directly is inaccurate.
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern SafeFileHandle CreateFileW(string fileName, uint access, uint share,
IntPtr security, uint disposition, uint flags, IntPtr template);
// Receive the return value as SafeFileHandle, not IntPtr, and close it reliably with using
// (leaving it as IntPtr leaks a kernel handle)
using var handle = CreateFileW(@"C:\ProgramData\MyApp\config.dat",
0x80000000 /*GENERIC_READ*/, 0, IntPtr.Zero, 3 /*OPEN_EXISTING*/, 0, IntPtr.Zero);
if (handle.IsInvalid)
{
int code = Marshal.GetLastWin32Error(); // Example: 5
var message = new Win32Exception(code).Message; // Example: Access is denied.
logger.LogError("CreateFileW failed: {Code} (0x{Code:X8}) {Message}",
code, code, message);
}
Win32Exception looks up the OS message string from a Win32 error code, so you can use it as-is for leaving both the code and the message in a log. The design question of at which layer to catch an exception and how to leave it in a log is covered in “Where Should catch and Logging Go in Exception Handling?”.
7. Conversion and Investigation Tools in Practice — A Copy-Paste Quick Reference
7.1. err.exe (Microsoft Error Lookup Tool)
A standalone error-lookup tool distributed by Microsoft. It walks a large number of header files such as winerror.h and ntstatus.h and lists definitions and messages that match the specified code.8
err 0x80070005
err 5
err 0xC0000005
A single number can hit in several headers (for example “5” matches definitions in various places besides Win32 ERROR_ACCESS_DENIED), so which of the candidates is plausible must be chosen by context. The download file name is versioned (Err_6.4.5.exe as of this writing), and note also that the code definitions are based on the headers at the time they were bundled.8
flowchart TB
accTitle: Choose err.exe search results by context
accDescr: err.exe walks a large number of header files and lists matching definitions, so when several candidates come up for the same number, choose which is plausible by context
in["Enter err 5"] --> scan["Walk a large number of headers"]
scan --> hits["Several definitions hit"]
hits --> pick["Choose a plausible candidate by context"]
Figure 17: err.exe is a cross-header search, so several candidates can come up, and the plausible one is chosen by context.
7.2. Commands built into Windows
What you can use with no extra install are certutil and net helpmsg. certutil’s -error option displays the message text that corresponds to an error code, and accepts either a hexadecimal HRESULT or a decimal.9
certutil -error 0x80070005
certutil -error 5
net helpmsg 5
net helpmsg is for a Win32 error code in decimal only, but in a Japanese environment the message comes back in Japanese, so you can use it as-is for an explanation to the user.
flowchart TB
accTitle: How to choose among the standard commands
accDescr: A decimal Win32 error code can be looked up with net helpmsg; a code that includes hex, including HRESULT, can be looked up with certutil's -error option
q{"The code you have is?"} -->|Decimal Win32| net["net helpmsg"]
q -->|Includes hex| cert["certutil -error"]
net -.-> jp["A Japanese message comes back"]
cert -.-> any["Accepts both hex and decimal"]
Figure 18: How to choose among the standard commands. A decimal Win32 error is net helpmsg; if it includes hex, certutil -error.
7.3. A collection of PowerShell one-liners
# Win32 error code → OS message string
[System.ComponentModel.Win32Exception]::new(5).Message
# → Access is denied.
# Negative decimal → hex notation (confirm the identity of an HRESULT)
'0x{0:X8}' -f -2147467259 # 0x80004005
# 0x8007xxxx → the Win32 error code in the low 16 bits
0x80070005 -band 0xFFFF # 5
# HRESULT → confirm the exception .NET maps
[System.Runtime.InteropServices.Marshal]::GetExceptionForHR(-2147024891)
# → UnauthorizedAccessException (0x80070005)
# Win32 error code → HRESULT (reproduce the wrap)
'0x{0:X8}' -f (0x80070000 -bor 32) # 0x80070020
7.4. WinDbg’s !error
To look up a code during dump analysis, WinDbg’s !error extension is quick. By default it interprets as a Win32 error code; pass 1 as the second argument and it interprets as an NTSTATUS.10
0:000> !error 5
Error code: (Win32) 0x5 (5) - Access is denied.
0:000> !error 0xc0000005 1
Error code: (NTSTATUS) 0xc0000005 - <Access violation>
In a crash dump, !analyze -v automatically displays the exception code (NTSTATUS), so the flow is to confirm the meaning from there with !error <code> 1.
flowchart TB
accTitle: Flow of confirming an exception code in WinDbg
accDescr: In a crash dump the analyze command automatically displays the exception code; pass that code to the error extension with a second argument of 1 and confirm the meaning as an NTSTATUS
dump["Open the crash dump"] --> an["Run !analyze -v"]
an --> exc["The exception code is displayed"]
exc --> chk["Confirm the meaning with !error code 1"]
Figure 19: In dump analysis, look up the exception code that !analyze -v displayed with !error and flag 1.
8. An Investigation Procedure — From Judging the Layer to Matching Against Context
Assemble the knowledge so far into a procedure for actually investigating an error code.
- Normalize the notation. If it is a negative decimal, convert it to 8-digit hex. Zero-pad hex shorter than 8 digits and read it.
- Judge which layer the code is. As in the judgment table below, the leading few digits almost decide it.
- Decompose and take out the essential code. A mechanical operation: the low 16 bits if 0x8007xxxx, strip the N bit if 0xDxxxxxxx.
- Look up the name and definition with a tool. Confirm the symbol name and message with err.exe, certutil, or
!error. - Match it against context. Identify which app, which operation, which API, failed against what, from the app log, the event log, and Procmon. The code is “the kind of failure”; context is “the place of the cause”.
flowchart TB
accTitle: Procedure for investigating an error code
accDescr: The investigation pattern of aligning the notation to hex, judging the layer from the leading few digits, decomposing and taking out the essential code, looking up the name and definition with a tool, then matching against context
fix["Normalize to hex"] --> judge{"Leading digits?"}
fix -.-> fixN["8 digits if negative"]
judge -->|Decimal| d1["Read as Win32 error"]
judge -->|Hex| hex{"Which hex prefix?"}
hex -->|0x8007| d2["Low 16 bits → decimal"]
hex -->|0xC / 0xD| nt{"NTSTATUS family?"}
nt -->|0xC| d3["Read as NTSTATUS"]
nt -->|0xD| d4["Strip N bit and read"]
d1 --> tool["Look up name/definition"]
d2 --> tool
d3 --> tool
d4 --> tool
tool --> ctx["Match context(Procmon)"]
Figure 20: The investigation pattern. Normalize the notation, judge the layer and decompose, look up the name, then match against context.
| Appearance | First candidate | How to decompose and convert |
|---|---|---|
| A 1-to-5-digit decimal (5, 1223, and the like) | Win32 error code | As-is to net helpmsg or err.exe |
| A negative decimal (-2147024891 and the like) | HRESULT | Convert to 8-digit hex, then the judgment of the rows below |
| 0x8007xxxx | HRESULT (FACILITY_WIN32) | Convert the low 16 bits to decimal and read as Win32 |
| 0x8004xxxx | HRESULT (FACILITY_ITF) | Look it up in the returning component’s documentation |
| 0x8000xxxx | HRESULT (FACILITY_NULL) | A generic code such as E_FAIL. Shift the weight to a context investigation |
| 0xCxxxxxxx | NTSTATUS (error) | !error <code> 1; convert to Win32 and read if needed |
| 0xDxxxxxxx | An NTSTATUS HRESULT map | Strip the N bit (0x10000000) and read as an NTSTATUS |
| A facility of its own such as 0x8024xxxx | An HRESULT specific to a feature area | Identify the area from the Facility value and go to dedicated materials (0x8024… is Windows Update)2 |
What is especially effective in step 5’s “matching against context” is Process Monitor’s Result column. Even if the app only shows “0x80070002”, Procmon tells you in one row “which process, against which path, was returned NAME NOT FOUND”. For looking things up on the event-log side, see also “An Introduction to Windows Event Log and ETW”.
9. Common Misreadings — Patterns That Send an Investigation the Long Way Around
Finally, misreading patterns that show up in actual consultations.
Misreading 1: Thinking 0x80004005 is “a code that indicates a specific cause”
E_FAIL is “Unspecified failure”, and the same value appears in Windows Update, networking, and databases. Trying every remediation that comes up when you search on this code is almost certainly the long way around. Narrow not from the code but from “which app, which operation, other logs at the same time”.6
Misreading 2: Not noticing that a negative decimal is an HRESULT
A case of searching a log that says “Error -2147467259 occurred” as-is, or being confused by “a minus error?”. When you see a negative, convert it to hex. That alone tells you it is 0x80004005 (E_FAIL), and connects to the knowledge of misreading 1.
Misreading 3: Looking up the whole 8 digits of 0x8007xxxx and not looking at the underlying Win32 error
The essence of 0x80070005 is “5 = access denied”. Thinking about “what Win32 error 5 means in the context of this operation” after taking out the low 16 bits reaches the core faster than searching on the whole 8 digits.
Misreading 4: Assuming “the same code = the same cause”
If you once had “error 5 was caused by antivirus”, you tend to jump to the same remediation on the next error 5. Even with the same code, if the API that failed and the target resource differ, the cause is a different thing. Confirming the meaning of the code and identifying the target with Procmon or similar are a set, every time.
Misreading 5: Confusing Win32 error 5 with 0xC0000005, and a STOP code with NTSTATUS
Treating ERROR_ACCESS_DENIED and STATUS_ACCESS_VIOLATION as the same because of the “5” connection sends the investigation in completely different directions — a permissions problem versus a program bug. Also, a blue-screen STOP code is a different system from NTSTATUS, so looking up 0x9F in an NTSTATUS table does not produce a meaningful answer.14
flowchart TB
accTitle: Error 5 and 0xC0000005 have different investigation directions
accDescr: Win32 error 5 should be investigated as a permissions problem, and NTSTATUS 0xC0000005 as a program bug; treating them as the same sends the investigation in a different direction
a["Win32 error 5"] --> ad["Investigate a permissions problem"]
b["NTSTATUS 0xC0000005"] --> bd["Investigate a program bug"]
a -.-> memo["Unrelated codes in different systems"]
b -.-> memo
Figure 21: Do not treat them as the same because of the “5” connection. Error 5 goes toward a permissions problem; 0xC0000005 toward a program bug.
10. Summary
- Windows error codes are a three-layer structure of Win32 error codes, HRESULT, and NTSTATUS. First judge which layer whose party returned the code.
- Notation wobble (decimal / hex / negative) can be aligned mechanically. Convert a negative to 8-digit hex and then read it.
- HRESULT is a structure of S/R/C/N/X bits + Facility (11 bits) + Code (16 bits), and 0x8007xxxx is the most important pattern, a wrapped Win32 error. Convert the low 16 bits to decimal and take out the essential code.
- A generic code such as 0x80004005 (E_FAIL) does not indicate a cause. The judgment to stop digging into the code and switch to a context investigation is possible precisely because you know the structure.
- You meet NTSTATUS as a crash exception code or in Procmon’s Result column. 0xC0000005 is an access violation, unrelated to Win32 error 5. A STOP code is yet another system.
- In .NET, an HRESULT is mapped to an exception type, and the original value remains in Exception.HResult. In P/Invoke, use SetLastError=true and Marshal.GetLastWin32Error as a set.
- The lookup tools are certutil -error and net helpmsg (standard), err.exe (a development machine), PowerShell one-liners, and WinDbg’s !error.
- The procedure is “normalize the notation → judge the layer → decompose → look up the name → match against context”. What a code tells you is the kind of failure; the place of the cause is what context tells you.
The next time you meet an unfamiliar error code, look at the leading few digits before you paste it into the search box. The low 4 digits if 0x8007, NTSTATUS if 0xC, convert to hex if negative — this 10-second decomposition largely decides the investigation time that follows.
Related Articles
- Reading Crash Dumps with WinDbg + SOS — A Practical Guide to Analysis After Collection
- An Introduction to Collecting Windows Crash Dumps - WER/ProcDump/WinDbg
- Where Should catch and Logging Go in Exception Handling?
- A Practical Guide to Process Monitor (ProcMon) — Pinpointing “Settings Not Applied” and “ACCESS DENIED” in 10 Minutes
- An Introduction to Windows Event Log and ETW — Putting Your Business App’s Logs on the OS’s Standard Mechanisms
Related Consulting Areas
KomuraSoft LLC handles incident investigation that starts from an error code — “I don’t know what this error code means”, “0x80070005 only appears in a specific environment” — error-handling design for apps that mix Win32 API, COM, and .NET, and identifying causes with crash dumps and Process Monitor. A consultation from a single screenshot of an error dialog is fine.
- Windows Application Development
- Bug Investigation & Root-Cause Analysis
- Technical Consulting & Design Review
- Contact Us
References
-
Microsoft Learn, Debug system error codes. An index to the list of Win32 system error codes (0–15999); getting the message for a code
GetLastErrorreturns with FormatMessage and the FORMAT_MESSAGE_FROM_SYSTEM flag; that WinINet/WinHTTP errors (the 12000s) are defined in this space; and investigation methods with the Microsoft Error Lookup Tool and the !err command. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
Microsoft Open Specifications, [MS-ERREF]: HRESULT. The HRESULT bit layout (S, R, C, N, and X bits, 11-bit Facility, 16-bit Code); that the N bit indicates an NTSTATUS value mapped into HRESULT space; and the list of facility codes including FACILITY_WINDOWS_UPDATE (36). ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Open Specifications, [MS-ERREF]: NTSTATUS. The NTSTATUS bit layout (2-bit Sev, C bit, N bit, 12-bit Facility, 16-bit Code); and that severity splits into four kinds: success (00), informational (01), warning (10), and error (11). ↩ ↩2 ↩3
-
Microsoft Learn, HRESULT_FROM_WIN32 macro. The definition of the winerror.h macro that maps a Win32 system error code to an HRESULT value. ↩ ↩2 ↩3
-
Microsoft Learn, Structure of COM Error Codes. The role of the HRESULT severity bit and the facility field; the values of FACILITY_NULL, FACILITY_RPC, FACILITY_ITF, FACILITY_WIN32, and FACILITY_WINDOWS; and that a FACILITY_ITF code has its meaning defined per interface and the same value can mean something different. ↩ ↩2 ↩3
-
Microsoft Learn, Common HRESULT values. That E_FAIL (0x80004005) is “Unspecified failure”; and definitions of frequently seen HRESULT values such as E_ACCESSDENIED (0x80070005), E_INVALIDARG (0x80070057), and E_OUTOFMEMORY (0x8007000E). ↩ ↩2 ↩3 ↩4
-
Microsoft Open Specifications, [MS-ERREF]: NTSTATUS values. The list of NTSTATUS values including STATUS_ACCESS_VIOLATION (0xC0000005), STATUS_DLL_NOT_FOUND (0xC0000135), STATUS_STACK_OVERFLOW (0xC00000FD), STATUS_HEAP_CORRUPTION (0xC0000374), and STATUS_BREAKPOINT (0x80000003). ↩ ↩2 ↩3
-
Microsoft Learn, The Microsoft Error Lookup Tool. That it is a standalone tool that displays the message text associated with a hexadecimal status code across various header files such as Winerror.h; that the download file name is Err_6.4.5.exe; and that you need to note that the bundled definitions are as of compile time. ↩ ↩2 ↩3
-
Microsoft Learn, certutil. That certutil’s -error option displays the message text associated with an error code, and that an error notation that includes a symbol name is used in a form such as 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND). ↩ ↩2
-
Microsoft Learn, !error. That WinDbg’s !error extension decodes and displays Win32, Winsock, NTSTATUS, and NetAPI error values; and that specifying 1 as the flag interprets as an NTSTATUS. ↩ ↩2
-
Microsoft Learn, How to: Map HRESULTs and exceptions. The mutual-mapping mechanism between COM HRESULT and .NET exceptions; the correspondence table such as E_NOTIMPL → NotImplementedException; that an HRESULT with no explicit mapping is converted to COMException; and that exception Message, Source, and the like are initialized from IErrorInfo information. ↩ ↩2
-
Microsoft Learn, RtlNtStatusToDosError function (winternl.h). That it is a function that converts an NTSTATUS code to the corresponding Win32 system error code; that ERROR_MR_MID_NOT_FOUND is returned when no correspondence is defined; and that a function that performs the reverse conversion does not exist. ↩ ↩2
-
Microsoft Learn, Last-Error Code. That the last-error code is held per thread; that it should be retrieved with GetLastError immediately after failure; that APIs that overwrite the code with 0 on success and APIs that do not are mixed; and that bit 29 is reserved for application-defined codes. ↩ ↩2
-
Microsoft Learn, Bug check code reference. The list of bug check codes (STOP codes) displayed on a blue screen, and how to display information about a code with WinDbg’s !analyze extension. That it is a numbering system of its own, separate from NTSTATUS, can be confirmed from the list. ↩ ↩2
-
Microsoft Learn, Marshal.GetLastWin32Error Method. That it is a way to retrieve the last-error code of a P/Invoke call that set the SetLastError flag; that P/Invoking GetLastError directly is not trustworthy because of overwrite by an API call inside the runtime; and that from .NET 6 GetLastPInvokeError is recommended. ↩
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Apps That Break on Resume from Sleep — How Windows Power Events Work and How to Build Business Apps That Survive Them
You opened the laptop and the business app's connections were dead — the cause is a design that never accounted for sleep. This article c...
DllMain and the Loader Lock — The Real Reason You're Told to "Do Nothing in DLL Initialization"
Why you must not call LoadLibrary or synchronize with other threads from DllMain. Drawing on primary sources, this article explains how t...
What "Not Responding" Really Is — How Windows Decides an App Has Hung, and How to Design Apps That Don't
Windows' "Not Responding" is a mechanism in which the OS judges that a window has not retrieved a message for 5 seconds and replaces it w...
The Win32 Thread Pool API — Concurrency Without Creating Threads, via CreateThreadpoolWork
Are you spawning CreateThread calls all over your native code? This article explains the Win32 thread pool API redesigned in Vista — the ...
Named Pipes in Practice — Windows' Standard IPC from Design to Security
A practical guide to named pipes, Windows' standard inter-process communication. This article organises, from primary sources, the choice...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Bug Investigation & Long-Run Failures
Topic page for intermittent failures, communication diagnosis, long-run crashes, and failure-path test foundations.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Bug Investigation & Root Cause Analysis
We investigate difficult production issues such as intermittent failures, long-run crashes, leaks, and communication stoppages.
Frequently Asked Questions
Common questions about the topic of this article.
- What does error 0x80004005 mean?
- 0x80004005 is the HRESULT E_FAIL, and it means "Unspecified failure". In other words it only indicates that "a failure occurred that cannot report a detailed reason"; it is not a code that represents the cause itself. The same 0x80004005 appears in unrelated places — networking, Windows Update, VBA, a database driver — for this reason. When you see this code, do not dig into the meaning of the code; narrow the cause from the context of which app and which operation produced it, and from other error information left in the event log or a detailed log.
- What is a negative error code such as -2147467259?
- It is a 32-bit HRESULT displayed as a signed decimal. An HRESULT sets the most significant bit on failure, so as a signed integer it is always negative. In PowerShell, '0x{0:X8}' -f -2147467259 converts it back to hexadecimal (in this example 0x80004005 = E_FAIL). When you see a negative number starting with -214… in a log or a script error message, the standard first step is to convert it to hex and then look it up.
- What is the easiest way to look up the meaning of an error code?
- With no extra install you can use net helpmsg 5 at the command prompt (for a Win32 error in decimal) and certutil -error 0x80070005. certutil also accepts a hexadecimal HRESULT and displays the symbol name and the message text. In PowerShell, [System.ComponentModel.Win32Exception]::new(5).Message gets the localized message. On a development machine, keep Microsoft's official error lookup tool err.exe (Microsoft Error Lookup Tool); it can search across Win32, HRESULT, and NTSTATUS and list matching definitions in one go.
- What kind of error is 0xC0000005?
- It is the NTSTATUS STATUS_ACCESS_VIOLATION, that is, an access violation (an illegal memory access). It is the code you see most often as the "Exception code" in the event log or in a crash dump when an application crashes, and it indicates a program bug such as a dereference of an invalid pointer or access to already-freed memory. It looks similar in name to Win32 error 5 (ERROR_ACCESS_DENIED = access denied), but it is an unrelated code in a different system, so do not confuse them. The reliable way to identify the cause is to capture a crash dump and analyze it in WinDbg.
- Why is the cause different every time, even with the same error code?
- Because an error code only represents "what kind of failure", and "what failed and why" is decided by the calling context. Error 5 (access denied), for example, is the same code for completely different causes — insufficient NTFS permissions, missing administrator privileges, an antivirus block, and so on. In a similar situation, if another process still has the file open you get a different code (error 32 = sharing violation), and reading the code correctly changes where you look. Error 2 (file not found) is also often not the main file but a dependent DLL or a settings file. Once you have looked up the meaning of the code, confirming which API failed against which resource with Process Monitor or similar is the short path to identifying the cause.