Diagnosing Why an App Fails on One Machine but Not Another: Environmental Access Diffing with ETW
An application installs and runs on one Windows machine and fails on another that looks identical. The error, if there is one, is generic. The two machines are the same build, the same version of the app, the same patch level. Something in the environment differs, and finding it by hand means comparing registry hives, file permissions, network reachability, and service state across two hosts, which is slow and error-prone.
This post describes a method that turns that comparison into a short, automatic list: record what the application actually accesses on a working machine using Event Tracing for Windows, run the same action on the broken machine, and diff the two captures. It also covers ProcDelta, a free open-source tool that implements the method.
The failure is almost always environmental
When the same application binary works in one place and fails in another, the cause is rarely the application. It is something the application depends on that is present or permitted on one host and absent or denied on the other. The common cases:
- A registry value present on working machines is missing on the broken one.
- An NTFS ACL on a path grants Modify on the working host and Read-only here.
- A required network host resolves and accepts a TCP connection on working machines and fails here, blocked by a proxy, a DNS misconfiguration, or a certificate-validation error.
- A service or scheduled task started on the working host and did not on this one.
Each of these is visible to the kernel at the moment the application touches the resource, together with whether the operation succeeded or failed. That is what makes ETW the right instrument: it records the application's own attempts and their results, rather than requiring a guess about which of thousands of environmental facts matters.
The method: record, reproduce, diff
The method has three steps. Record a known-good baseline: on a machine where the app works, capture every registry, file, network, and process access the app makes while performing the action, with the success or failure of each. Reproduce on the broken machine: run the same action under the same capture. Diff: compare the two, and report only the accesses that disagree.
The reason this works is that the overwhelming majority of what the application does is identical on both machines. The same keys are read, the same files opened, the same hosts contacted. Only a handful of accesses differ, and the difference between success and failure on those accesses is the diagnosis. A chatty application that generates thousands of accesses typically produces a diff of a few entries.
What gets captured
The kernel view comes from a private kernel ETW session (the System Trace Provider group, available on Windows 8 and later) rather than the legacy NT Kernel Logger, so it coexists with PerfView, xperf, and other capture tools. Four kernel providers supply the core signal:
| Provider | Captured |
|---|---|
Microsoft-Windows-Kernel-Process | spawn and exit, with exit codes |
Microsoft-Windows-Kernel-FileIO | create and delete, with NTSTATUS result |
Microsoft-Windows-Kernel-Registry | query, set, open, create, and delete, with NTSTATUS result |
Microsoft-Windows-Kernel-Network | TCP connect attempts, IPv4 and IPv6 |
A second, user-mode session adds application-runtime context that the kernel layer does not carry:
| Provider | Captured |
|---|---|
Microsoft-Windows-Services | service start and stop, service-control-manager errors |
Microsoft-Windows-WinINet | HTTP and HTTPS requests, including proxy, certificate, and connection failures |
Microsoft-Windows-CAPI2 | certificate-chain validation failures |
.NET Common Language Runtime | managed unhandled exceptions and assembly-load failures |
The NTSTATUS result on the registry and file operations is the important part. It is what lets the diff distinguish an access that succeeded on the baseline from the same access failing here, which is the difference that explains the break. For more on how the kernel network providers work, see ETW Network Telemetry on Windows; for the broader set of performance providers, see Troubleshooting Windows Performance with ETW.
Making a baseline portable between machines
A baseline recorded on one host has to be meaningful on another, which requires two pieces of normalization.
Paths are tokenized at capture time. User-profile and system folders become tokens such as <USER>, <APPDATA>, <LOCALAPPDATA>, <PROGRAMFILES>, <WINDOWS>, and <SYSTEM32>, so a path under one user's profile matches the equivalent path under a different user's profile on the other machine.
The capture follows the process tree. A process is tracked if its image matches the operator-supplied pattern, or if its parent is already tracked, so a service spawned by the target application is captured without extra configuration. Every event from either session is filtered against the tracked set before it is recorded, which keeps the baseline scoped to the application under investigation.
The baseline is aggregated, not a raw log
Raw events are folded into unique (Kind, Target, Operation, Detail) tuples. Each unique combination becomes one row with an access count and the most recent observed result, serialized to JSON with a schema version. A typical baseline is 50 to 200 KB rather than a multi-megabyte trace.
For registry values, the recorder does a user-mode read-back, SHA-256 hashes the value bytes, and stores the hash and the registry type name, never the value itself. The hash lets the diff detect that a value changed between the baseline and the broken machine without the value content ever leaving the recording host. No application data or PII is stored in the baseline.
The diff and its classifications
For each access in the live capture, the diff engine looks up the same target in the baseline and classifies the result:
| Baseline | Live | Classification | Severity |
|---|---|---|---|
| SUCCESS | non-SUCCESS | Regression | High |
| present, hash X | present, hash Y | Value drift | Medium |
| present | missing | Missing dependency | Medium |
| not present | non-SUCCESS | Novel failure | Low |
| same on both sides | n/a | (suppressed) | n/a |
A regression, an access that worked on the baseline and fails here, is the strongest signal and ranks highest. Value drift flags a configuration value that changed. A missing dependency is a resource the application expected and did not find. A novel failure is an access that fails here and was not present in the baseline at all, ranked lowest because it may be incidental.
Each candidate is then enriched with the current live state of that target on the broken machine: the registry value content and ACL, the file's presence, ACL, and size, or a live TCP probe to the host and port. That live state is the actionable line in the report, because it tells the operator not just that the access differs but what the resource looks like right now. Candidates are ranked by severity, then by proximity to the moment the process exited (accesses within the last two seconds of activity rank higher, since a failure just before exit is often the cause), then alphabetically for a deterministic report.
ProcDelta: the method as a tool
ProcDelta is a standalone Windows application that implements this method. It is open source under the Apache License 2.0, with signed release builds published on GitHub, and it runs on its own with no install, no cloud, and no telemetry. It has three tabs. On Record, the operator picks a process name, describes the action, starts capture, performs the action on a working machine, and saves a .baseline.json. On Compare, on the broken machine, the operator loads the baseline, performs the same action, and runs the diff; the report lists the disagreements ranked by severity with live state alongside, and exports as Markdown for attaching to a ticket. A Help tab covers the workflow and limitations.
The diagnosis is deterministic. The diff engine is the small set of classification rules above, with no inference that changes between runs, so the same baseline and the same live capture always produce the same report.
Limitations
The approach has boundaries worth stating plainly. It is Windows-only, because ETW is a Windows subsystem. It requires Administrator, because a kernel session cannot start otherwise. A host allows eight concurrent kernel sessions; the tool uses one private kernel session and coexists with others, and only fails to start when all eight slots are already in use. Application-runtime coverage is limited to the four user-mode providers above, so surfaces such as WMI, Group Policy, and AppX are not captured. And a baseline encodes only what ETW can see: if the working machine's good state depends on something invisible to ETW, such as an in-memory cache or a session token, the baseline captures only the visible part. Recording on a freshly set-up machine produces the most portable baselines.
Frequently asked questions
Why does an application work on one Windows machine and fail on another?
The difference is almost always environmental rather than in the application: a registry value present on one host and missing on the other, an NTFS ACL that grants Modify in one place and Read-only in the other, a network host that is reachable on one machine and blocked by a proxy or failed certificate check on the other, or a service or scheduled task that started in one environment and not the other.
What is differential environmental diagnosis?
A method for finding an environmental cause of an application failure. Record the kernel's view of what the application accesses during a known-good run, run the same action on the broken machine, and diff the two. Because most of what the application does is identical on both machines, the diff is short and points at the difference that explains the failure.
Which ETW providers capture what an application accesses?
Microsoft-Windows-Kernel-Registry captures registry operations with their NTSTATUS result, Microsoft-Windows-Kernel-FileIO captures create and delete with result, Microsoft-Windows-Kernel-Process captures spawn and exit with exit codes, and Microsoft-Windows-Kernel-Network captures TCP connect attempts. User-mode providers (WinINet, CAPI2, Services, and the .NET CLR) add HTTP, certificate-validation, service-control, and managed-exception context.
Is ProcDelta free?
Yes. ProcDelta is open source under the Apache License 2.0, with signed release builds on GitHub. It runs standalone with no cloud and no telemetry, and requires Administrator because it starts a kernel ETW session.
Relationship to ET Ducky
ProcDelta is the standalone, manual, single-machine version of one investigation pattern. ET Ducky is a cross-platform diagnostic agent that uses ETW on Windows and eBPF on Linux for continuous, fleet-wide kernel observability, and automates this kind of investigation across many machines rather than one at a time. The two are independent projects; the tool is useful on its own, and the method it demonstrates is the same one the agent applies at scale.