App Control event IDs, and reading the CodeIntegrity log properly
Event 3076, 3077, 3089, 8028 and 8029 explained, which of the two logs each lives in, and how to correlate a block with the signature information that explains it.
Most App Control troubleshooting is a reading exercise. The information you need is almost always in the event log already, in a shape that is not obvious, split across two logs that are not next to each other.
If you are searching for WDAC events, this is the same thing. Windows Defender Application Control was renamed App Control for Business, and the logs, the channels and the event IDs did not change with it.
This is what the events mean and how to put them together.
There are two logs, not one
App Control writes to two locations in Event Viewer, and which one depends on what was blocked.
Applications and Services Logs, Microsoft, Windows, CodeIntegrity, Operational covers policy activation and the control of executables, DLLs and drivers.
Applications and Services Logs, Microsoft, Windows, AppLocker, MSI and Script covers MSI installers, scripts and COM objects.
That second log is the one people miss. If a PowerShell script or an MSI is being refused and the CodeIntegrity log looks quiet, you are reading the wrong log. Note also that the AppLocker MSI and Script events are not present on Windows Server Core edition.
The events you will actually use
In the CodeIntegrity Operational log
| Event | What it means |
|---|---|
| 3076 | The main block event for a policy in audit mode. The file would have been blocked if the policy were enforced. |
| 3077 | The main block event for an enforced policy. The file did not pass and was blocked. |
| 3089 | Signature information for a file that was blocked or audit blocked. One event per signature. |
| 3033 | The file did not meet policy requirements. Often means the signature is revoked, or a signature with the Lifetime Signing EKU has expired. Should appear alongside a 3077 if App Control caused it. |
| 3034 | The audit mode equivalent of 3033. Not common. |
| 3004 | Usually a kernel driver trying to load with an invalid signature. Can occur with or without an App Control policy present. |
| 3099 | A policy has been loaded. The details include the policy options, which makes this the event to read when you want to know what is actually active. |
In the AppLocker MSI and Script log
| Event | What it means |
|---|---|
| 8028 | A script host asked App Control about a file it was about to run, and the policy was in audit mode. The file ran, but would not have passed an enforced policy. |
| 8029 | The enforced equivalent of 8028. |
| 8036 | A COM object was blocked. |
| 8037 | A script host checked a script and it passed the policy. |
| 8038 | Signature information correlated with an 8028 or 8029. |
| 8039 | A packaged app was allowed to install or run because the policy is in audit mode, but would have been blocked if enforced. |
| 8040 | A packaged app was prevented from installing or running. |
The detail that changes how you read 8029
Event 8029 says a script was blocked. That is not quite what happens.
Microsoft's own note on it: "While this event says that a script was blocked, the script hosts control the actual script enforcement behaviour. The script host may allow the file to run with restrictions and not block the file outright."
The important case is PowerShell, which runs a script not allowed by your policy in Constrained Language Mode rather than refusing it. So an 8029 for a PowerShell script does not mean the script did not run. It means it ran without the capabilities Constrained Language Mode removes, which is a different failure and produces different symptoms. A script that half works, or fails on one specific call, is often this.
Correlating a block with its signature information
A 3077 on its own tells you a file was blocked. It does not tell you why the signature was not acceptable. That is what 3089 is for, and the two are joined by an identifier that is easy to overlook.
Each 3089 event carries the signature information for one signature on the file, plus a total count and an index. An unsigned file produces exactly one 3089 with a TotalSignatureCount of 0, which is itself useful: it confirms the file is unsigned rather than signed by something you do not trust.
The events are correlated using the Correlation ActivityID found in the System portion of the event, and 3089 correlates with 3004, 3033, 3034, 3076 and 3077. In the script log, 8038 correlates with 8028 and 8029 the same way.
Practically, that means the useful query is not "show me the 3077s". It is "show me the 3077s and the 3089s, grouped by correlation".
# Blocks and their signature events, from the last day
$events = Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-CodeIntegrity/Operational'
Id = 3076, 3077, 3089
StartTime = (Get-Date).AddDays(-1)
}
# Pull the correlation id out of the event XML
function Get-Correlation {
param($Event)
([xml]$Event.ToXml()).Event.System.Correlation.ActivityID
}
$events |
Group-Object { Get-Correlation $_ } |
ForEach-Object {
Write-Host "`nCorrelation $($_.Name)" -ForegroundColor Cyan
$_.Group |
Sort-Object Id |
Select-Object TimeCreated, Id, Message |
Format-List
}
That grouping is the difference between a list of blocks and an explanation of them.
Diagnostic events for managed installer and reputation
If your policy uses managed installer or the Intelligent Security Graph, three more events explain why something was or was not authorised by them.
| Event | What it means |
|---|---|
| 3090 | Optional. A file was allowed to run based purely on ISG or managed installer. |
| 3091 | A file did not have ISG or managed installer authorisation, and the policy is in audit mode. |
| 3092 | The enforced equivalent of 3091. |
The details on these carry the fields that actually answer the question: ManagedInstallerEnabled, PassesManagedInstaller, SmartlockerEnabled, PassesSmartlocker, AuditEnabled and PolicyName. If you are debugging why an application your deployment tool installed is still being refused, PassesManagedInstaller is the field to look at.
Two operational notes on these:
3090 is off by default. To turn on the allow events, create a TestFlags value and restart:
$ci = 'hklm\system\currentcontrolset\control\ci'
reg add $ci -v TestFlags -t REG_DWORD -d 0x300
Events 3091 and 3092 are inactive on some Windows versions and are also turned on by that command.
Managed installer is noisy. Microsoft warns that with managed installer enabled, 3091 events can fire in large numbers, and that anyone forwarding to Log Analytics may need to filter them out to avoid high cost. If you are shipping these to a SIEM, decide what you are keeping before you turn it on.
These events are also reported per active policy, so multiple events for the same file are expected rather than duplicates.
Policy activation events, and why 3095 matters
When a policy will not apply, the activation events tell you why.
| Event | What it means |
|---|---|
| 3095 | The policy cannot be refreshed and must be rebooted instead. |
| 3096 | The policy was not refreshed because it is already up to date. Details include the policy options. |
| 3097 | The policy cannot be refreshed. |
| 3099 | A policy has been loaded. |
| 3100 | The policy was refreshed but was unsuccessfully activated. Retry. |
| 3101 and 3102 | A refresh started and finished, for N policies. |
| 3103 | The system is ignoring the refresh, for example an inbox Windows policy that does not meet the conditions for activation. |
3095 is the one that answers the most common complaint, which is that a policy change was deployed and nothing happened. A refresh is not always possible, and when it is not, the device needs a restart. Option 16 Enabled:Update Policy No Reboot is what allows future updates to apply without one, on Windows 10 1709 and later or Windows Server 2019 and later.
The block message users report
The dialog a person sees does not name the event, so it is worth knowing what maps to what. Depending on the Windows version they may report a message referring to Windows Defender Application Control, or an older one naming Device Guard.
Your organization used Windows Defender Application Control to block this app.
Either way, the device side evidence is a 3077 in the CodeIntegrity Operational log if the policy is enforced, or an 8029 in the AppLocker MSI and Script log if it was a script. Ask for the time and the executable name and you can find the correlated 3089 in seconds.
What App Control does not log
Worth being explicit, because plans get built on the assumption that it does.
Allowed files produce no events by default. App Control does not generate events when a binary is allowed. If you want an allowlist audit trail of everything that ran, this is not it. The optional 3090 covers files authorised by managed installer or ISG, not everything.
COM object failures from GUID mismatches are silent. .NET performs an extra check that the registered COM object GUID matches the calculated one, and does not load the object if they differ. Microsoft states there is no logging or other events when a COM object fails to load due to that check, it only happens when policy is enforced on user mode code, and you cannot detect it in audit mode. If an application starts behaving oddly only under enforcement and nothing appears in either log, this is a candidate.
A reading order that works
When something is blocked and you need an answer quickly:
- Establish which log. Executable, DLL or driver goes to CodeIntegrity Operational. Script, MSI or COM goes to AppLocker MSI and Script.
- Find the block. 3077 or 3076 in the first, 8029 or 8028 in the second. That gives you the file and the policy.
- Take the Correlation ActivityID and pull the 3089, or 8038, events. That gives you the signature position, including whether the file is signed at all.
- If the policy uses managed installer or ISG, check for 3091 or 3092 and read
PassesManagedInstaller. - Check 3099 for what policy is actually loaded and with which options, rather than what you believe you deployed.
Step 5 is the one that resolves arguments. What is on the device and what is in your deployment tool are not always the same thing.
At fleet scale this stops being a per-device exercise, which is the whole reason we built a control plane for it. If you are working through it manually, why Intune deployed apps still get blocked covers the most common single cause.
Reading these events one at a time is workable on a handful of machines and stops being workable quickly. PoliEze turns them into something you can act on, with each event already attached to an application and the team that owns it, so a block is a decision waiting rather than a hash somebody has to identify first.
Sources
Questions about this
Where do I find WDAC events in Windows?
What is the difference between event 3076 and 3077?
Why is my blocked script not in the CodeIntegrity log?
What does event 3089 mean?
Does App Control log files that it allows?
Related reading
Audit mode to enforcement, and what audit mode does not tell you
Audit mode is not a perfect dress rehearsal. Four things are enforced or changed even in audit, and one failure mode produces no events at all.
The missing control plane for App Control for Business
Windows already enforces. What no vendor ships is the layer that decides what to trust, records who approved it, and takes it away again when it is no longer needed.
Every application control product identifies files the same way
The file identity model is common to all of them. What separates one product from another is the operating model around it, which is what evaluations skip.