mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-25 13:10:13 +00:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44dd8161df | |||
| c84a1c23eb | |||
| 4a7eb2749d | |||
| a536553ee3 | |||
| ce202c6d47 | |||
| 74902bf185 | |||
| 4f021852ee | |||
| 7a3516e570 | |||
| 6907a41a84 | |||
| f4a5eac539 | |||
| 09492e908c | |||
| f432353efa | |||
| 1daf631a02 | |||
| d0190f443d | |||
| e96f49799f | |||
| 4864b2d21d | |||
| 56c37866d1 | |||
| 2852418413 | |||
| 268d8652f1 | |||
| 66f5543fb7 | |||
| 21d200a1e4 | |||
| 8c0cb2c423 | |||
| 84d06e1cf6 | |||
| 88dfd331dd | |||
| d294415d29 | |||
| 76af1234ed | |||
| 0515523e49 | |||
| a47e94f9a5 | |||
| ecfa59d049 | |||
| 6c50a54249 | |||
| 6e3487756a | |||
| dbb154bae9 | |||
| c41c2327ab | |||
| 83effea592 | |||
| 1a4a37b77a | |||
| f56851fc9f | |||
| cbe410cbbc | |||
| 43ddc60111 | |||
| 82b26b4ab9 |
+1
-1
Submodule .claude/commands updated: 0d524a5695...3e4d919c98
@@ -76,12 +76,23 @@ jobs:
|
||||
- name: Reserve build number (counter blob CAS)
|
||||
id: version
|
||||
shell: pwsh
|
||||
# Input and secret reach the script only as environment variables, never as script
|
||||
# text: ${{ }} is substituted before pwsh parses the block, so a crafted
|
||||
# build_number could otherwise break out of the quotes and run arbitrary code.
|
||||
env:
|
||||
BUILD_NUMBER_OVERRIDE: ${{ inputs.build_number }}
|
||||
AGENT_SAS_TOKEN: ${{ secrets.AZURE_BLOB_AGENT_SAS_TOKEN }}
|
||||
run: |
|
||||
$override = 0
|
||||
if ('${{ inputs.build_number }}' -ne '') { $override = [int]'${{ inputs.build_number }}' }
|
||||
if ($env:BUILD_NUMBER_OVERRIDE -ne '') {
|
||||
if ($env:BUILD_NUMBER_OVERRIDE -notmatch '^\d+$') {
|
||||
throw "build_number must be a positive integer, got '$($env:BUILD_NUMBER_OVERRIDE)'"
|
||||
}
|
||||
$override = [int]$env:BUILD_NUMBER_OVERRIDE
|
||||
}
|
||||
|
||||
$counterUrl = "https://autopilotmonitoreu.blob.core.windows.net/agent/buildcounter-v$($env:LINE_MAJOR).txt"
|
||||
$buildNumber = & ./.github/scripts/Request-BuildNumber.ps1 -CounterUrl $counterUrl -SasToken '${{ secrets.AZURE_BLOB_AGENT_SAS_TOKEN }}' -Override $override
|
||||
$buildNumber = & ./.github/scripts/Request-BuildNumber.ps1 -CounterUrl $counterUrl -SasToken $env:AGENT_SAS_TOKEN -Override $override
|
||||
|
||||
$csproj = Get-Content $env:CSPROJ_PATH -Raw
|
||||
if ($csproj -match '<VersionPrefix>(\d+\.\d+)</VersionPrefix>') {
|
||||
|
||||
@@ -54,6 +54,12 @@ jobs:
|
||||
ajv validate -s rules/schema/ime-log-pattern.schema.json -d "$f" --spec=draft2020 -c ajv-formats
|
||||
done
|
||||
|
||||
# guardrails.json values are baked verbatim into generated TypeScript that is
|
||||
# auto-committed to main and shipped to the portal - the schema forbids control
|
||||
# and line-terminator characters so a "data" PR can never become code.
|
||||
- name: Validate Guardrails
|
||||
run: ajv validate -s rules/schema/guardrails.schema.json -d rules/guardrails.json --spec=draft2020 -c ajv-formats
|
||||
|
||||
- name: Combine rules into dist/
|
||||
run: node rules/scripts/combine.js
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ title: CMTrace Time Resolution — Per-Line Self-Anchoring
|
||||
description: How the agent converts bias-less CMTrace local times to UTC without trusting any process's timezone belief — each provably fresh line anchors its own offset against the agent clock on the 15-minute grid; everything not provably fresh falls back uniformly and says so.
|
||||
resource: src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime
|
||||
tags: [agent, ime, cmtrace, timestamps, timezone, calibration, provenance]
|
||||
timestamp: 2026-08-20
|
||||
timestamp: 2026-08-30
|
||||
---
|
||||
|
||||
# Problem
|
||||
@@ -86,6 +86,40 @@ with the process zone).
|
||||
* Mixed pairs remain possible across a restart (start resolved from backlog fallback,
|
||||
completion line-anchored) and are recognizable via provenance.
|
||||
|
||||
# Line parsing (bounded cost)
|
||||
|
||||
`CmTraceLogParser.TryParseLine` (Shared) is the single CMTrace line parser for the agent
|
||||
(`ImeLogTracker`, `LogParserCollector`, `StallProbeCollector`) and the backend
|
||||
(`TestLogPatternFunction`). Both feed it input an outsider influences: tenant-supplied
|
||||
sample lines (200 x 8 KB per request) on the backend, and on the agent every tailed log
|
||||
file, including entries that IME assembles from installer or script output. It is
|
||||
therefore not one regex. The former shape — a greedy `(?<message>.*)` in front of the
|
||||
`]LOG]!><time="` literal, unanchored, no match timeout — re-tried every `<![LOG[`
|
||||
occurrence as a fresh start and backtracked the message group across every later character
|
||||
for each: quadratic in line length (an 8 KB line of repeated prefixes and `]` filler cost
|
||||
~7 million engine steps; a 1 MB assembled entry would have spun the SYSTEM agent for
|
||||
hours and blacked out IME tracking for the rest of the enrollment).
|
||||
|
||||
The parser reproduces exactly what that regex returned, in linear time: the message begins
|
||||
after the first `<![LOG[` (a later start could only match when the first one does, since
|
||||
`.*` spans the difference) and ends at the last `]LOG]!><time="` occurrence whose trailer
|
||||
parses — earlier occurrences are tried in turn, which is the order greedy backtracking
|
||||
visited them. The trailer is checked with a `\G`-anchored regex that has no ambiguous
|
||||
quantifier and a 1-second match timeout as a backstop; a timeout means "not a CMTrace
|
||||
line" (callers already have that path: raw-text matching in the tracker, `parse_failed`
|
||||
on the backend), never an exception. `CmTraceLogParserTests` pins the equivalence against
|
||||
the legacy regex over the shapes that matter (multiline messages, bias suffix, trailer
|
||||
literal inside the message, a last trailer that fails to parse, BOM prefix) and the
|
||||
reported worst case at 200 lines.
|
||||
|
||||
On the agent, `ImeLogTracker` additionally caps an assembled multiline entry at 100 raw
|
||||
lines **and** 1 MB of characters (`MaxMultiLineBufferChars`). A capped entry is dropped
|
||||
and logged at **Warning** — visible at the default `Info` level, so a real IME entry that
|
||||
large shows up in the client log and can be acted on. The remaining physical lines of a
|
||||
dropped entry are skipped until it closes or a new `<![LOG[` entry begins; previously they
|
||||
fell through to raw-text pattern matching, so fragments of a discarded entry could fire
|
||||
patterns without CMTrace context.
|
||||
|
||||
# Backend regression tripwire
|
||||
|
||||
The backend watches for the failure mode this design eliminates. On every terminal ingest
|
||||
@@ -129,3 +163,6 @@ precisely so an anchoring regression can never surface as a customer finding.
|
||||
* `tasks/todo.md` (2026-08-20) — full evidence trail: field measurements, the revert,
|
||||
the two-writer-era fixture, and the disproven foreign-binary hypothesis (the SHA
|
||||
mismatch was the known release race against the 5-minute AdminConfiguration cache).
|
||||
* `CmTraceLogParser.TryParseLine` + `CmTraceLogParserTests` — the linear parser and its
|
||||
legacy-regex equivalence oracle; `ImeLogTracker.LogProcessing.cs` multiline buffering
|
||||
(`MaxMultiLineBufferLines` / `MaxMultiLineBufferChars`, `skippingDroppedEntry`).
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
type: concept
|
||||
title: Diagnostics Package
|
||||
description: What the agent's diagnostics ZIP contains and why — the built-in section catalog shared with the portal, the RealmJoin and Device Preparation collection gates, the configured-path guards, caps, and the package manifest.
|
||||
description: What the agent's diagnostics ZIP contains and why — the built-in section catalog shared with the portal, the RealmJoin and Device Preparation collection gates, the configured-path guards, the handle-validated reads that close the enumerate-then-open junction race, caps, and the package manifest.
|
||||
resource: agent
|
||||
tags: [diagnostics, realmjoin, device-preparation, guardrails, portal]
|
||||
timestamp: 2026-08-23
|
||||
tags: [diagnostics, realmjoin, device-preparation, guardrails, portal, security]
|
||||
timestamp: 2026-08-30
|
||||
---
|
||||
|
||||
# Schema
|
||||
@@ -90,10 +90,50 @@ validates **admin-typed** paths: allowlisted prefixes, wildcard only in the last
|
||||
`C:\Users` blocked except the token's `AppData\Local|Roaming`. The catalog is reviewed code,
|
||||
exactly like the five sections that never went through the guard before — and
|
||||
`%ProgramData%\RealmJoin` is not on the allowlist at all. What does apply to every section:
|
||||
the reparse-point skip (files and directories), the `.evtx` channel export, and the caps
|
||||
the handle-validated read (next section), the `.evtx` channel export, and the caps
|
||||
(100 MB per file, 500 MB total, 5000 files) tracked globally across sections by
|
||||
`BudgetTracker`, with every skip recorded in the manifest and `_TRUNCATED.txt`.
|
||||
|
||||
## Reads are validated on the handle, not the path
|
||||
|
||||
Every guard above judges a path string. Reading the bytes from that string later —
|
||||
`File.GetAttributes` on the candidate, then a `FileStream` open after earlier files have been
|
||||
compressed — leaves a window: under any folder a local user can write to (the user-profile
|
||||
token paths, `ProgramData` subtrees, `C:\Install\Log`) that user can turn a subdirectory into
|
||||
a junction in between. An attribute check on the file resolves through a junction in a
|
||||
*parent* component, and SYSTEM would copy the junction target into the package.
|
||||
`PinnedSourceFolder` (`Monitoring/Runtime/PinnedSourceFolder.cs`, Win32 declarations in
|
||||
`Monitoring/Interop/FileNativeMethods.cs`) closes the window by deciding on the handle that
|
||||
is read:
|
||||
|
||||
* `AddLogFiles` pins the section folder once. It is opened with
|
||||
`FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS`, must not be a reparse point
|
||||
itself, and its `GetFinalPathNameByHandle` result — every reparse point resolved — must
|
||||
equal the validated lexical path (8.3 names expanded through `GetLongPathName`, so a
|
||||
`%TEMP%`-style short name is not mistaken for a junction). Anything else skips the section
|
||||
with `FOLDER REJECTED (<reason>)`; a missing folder stays `FOLDER MISSING`. The handle is
|
||||
held, with `FILE_LIST_DIRECTORY` access and no delete share, until the section is done:
|
||||
NTFS refuses to rename or delete a directory while it or anything beneath it is open, so
|
||||
the validated chain cannot be swapped underneath the build either.
|
||||
* Every candidate file is opened by `PinnedSourceFolder.TryOpenFile` with
|
||||
`FILE_FLAG_OPEN_REPARSE_POINT` — a symlink at the final component is opened as itself and
|
||||
refused (`SKIPPED (reparse point)`) — and the handle's final path must be exactly
|
||||
`<canonical folder>\<relative path>`. A subdirectory that became a junction after
|
||||
enumeration resolves elsewhere: `SKIPPED (resolved outside validated folder)`, naming only
|
||||
the requested path in the manifest, never the resolved one. The length charged against the
|
||||
caps and the stream copied into the archive come from that same handle; any other open
|
||||
failure is `FAILED (open): <path>: <reason>`.
|
||||
* `CollectFilesNoReparseDirs` still skips reparse-point directories while enumerating — as a
|
||||
candidate filter and to keep the walk out of junction loops — but nothing it decides is
|
||||
trusted at copy time.
|
||||
* The `.evtx` channel export is the agent's own file under an unguessable name in SYSTEM's
|
||||
temp folder and is streamed as-is. Hard links are out of scope: Windows refuses to create
|
||||
one to a file the caller cannot write.
|
||||
|
||||
The race itself is pinned by `DiagnosticsPackageServiceTests`: the `BeforeSourceFileOpen`
|
||||
seam swaps a subdirectory for a junction after enumeration and before the open, and the
|
||||
archive must not carry the file.
|
||||
|
||||
## Configured paths: global ∪ tenant
|
||||
|
||||
`GetAgentConfigFunction.MergeDiagnosticsLogPaths` sends global paths (Global Admin,
|
||||
@@ -120,6 +160,8 @@ same-named folder share one ZIP folder.
|
||||
catalog ids; `sectionFolderOverrides` covers any other section; `devicePreparationProbe`
|
||||
replaces the registry read (seam callers default to "not WDP", so existing rigs never touch
|
||||
the registry). `UserProfileResolver.SetForTesting(path|null)` / `Reset()` drive the token.
|
||||
`BeforeSourceFileOpen` fires with each candidate path after enumeration and before its open
|
||||
— the point at which the junction-race test swaps the directory.
|
||||
* Catalog invariants live in the backend test project (`DiagnosticsBuiltInSectionsTests`),
|
||||
because Shared has no test project.
|
||||
|
||||
@@ -139,9 +181,11 @@ entry is removed.
|
||||
# Citations
|
||||
|
||||
* `src/Shared/AutopilotMonitor.Shared/Models/Diagnostics/DiagnosticsBuiltInSections.cs` — the catalog
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/DiagnosticsPackageService.cs` — `BuildArchiveBytes`, `AddBuiltInSection`, `IsSectionActive`, caps, evtx export
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/DiagnosticsPackageService.cs` — `BuildArchiveBytes`, `AddBuiltInSection`, `IsSectionActive`, `AddLogFiles` / `AddLogFile` / `OpenSource`, caps, evtx export
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Runtime/PinnedSourceFolder.cs` — pinned folder handle, `TryOpenFile`, `SourceFile`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Interop/FileNativeMethods.cs` — `CreateFileW`, `GetFileInformationByHandle`, `GetFinalPathNameByHandleW`, `GetLongPathNameW`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Configuration/RemoteConfigMerger.cs` — `EnableRealmJoinWatcher` mirror
|
||||
* `src/Backend/AutopilotMonitor.Functions/Functions/Diagnostics/GetDiagnosticsPathsFunction.cs` — `GET diagnostics/paths`
|
||||
* `src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAgentConfigFunction.cs` — `MergeDiagnosticsLogPaths`
|
||||
* `src/Web/autopilot-monitor-web/components/diagnostics/` — `DiagnosticsPathRow`, `BuiltInSectionsList`, `builtInSectionDisplay`
|
||||
* Tests: `DiagnosticsPackageServiceTests` (RealmJoin on/off, no user session, bootstrapper evtx, probe failure), `DiagnosticsBuiltInSectionsTests`, `GetDiagnosticsPathsPayloadTests`, `GetAgentConfigFunctionTests.MergeDiagnosticsLogPaths_*`
|
||||
* Tests: `DiagnosticsPackageServiceTests` (RealmJoin on/off, no user session, bootstrapper evtx, probe failure, junction swapped in after enumeration, junction as section folder), `PinnedSourceFolderTests` (final-path equality, short names, junction in the chain, symlink at the final component, rename refused while pinned), `DiagnosticsBuiltInSectionsTests`, `GetDiagnosticsPathsPayloadTests`, `GetAgentConfigFunctionTests.MergeDiagnosticsLogPaths_*`
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
type: concept
|
||||
title: IME Log Tracker — Anchored Patterns and the Bounded Read Loop
|
||||
description: Why every shipped IME log pattern starts with '^', why the on-device matcher reads lines through a byte-bounded reader with a per-line matching budget, and what an operator sees when a hostile log file is tailed — the IME Logs folder is writable by standard users and the SYSTEM tracker must stay linear on whatever lands there.
|
||||
resource: src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime
|
||||
tags: [agent, ime, regex, redos, log-tracker, reliability, security]
|
||||
timestamp: 2026-08-30
|
||||
---
|
||||
|
||||
# Problem
|
||||
|
||||
`ImeLogTracker` tails every file in `%ProgramData%\Microsoft\IntuneManagementExtension\Logs`
|
||||
whose name matches the IME log families and matches each entry against every active pattern of
|
||||
the shipped pack (`rules/ime-log-patterns/*.json` → `rules/dist/ime-log-patterns.json`, embedded
|
||||
in agent and backend, delivered live through the config channel). That folder is writable by a
|
||||
standard user, so the input is untrusted: anyone signed in during enrollment can append lines.
|
||||
|
||||
Before 2026-08-30 three things multiplied:
|
||||
|
||||
* **Unanchored patterns with unbounded lazy gaps.** `\[Win32App\]\[EspManager\] … registered.*?App name: (?<name>.*?)$`
|
||||
re-scans its `.*?` tail at every offset where the literal prefix occurs; repeating the prefix
|
||||
*m* times in one line of *n* chars costs O(m·n). `IME-APP-VERSION` chained two lazy scans
|
||||
(`.*?"DetectedIdentityVersion":\{[^}]*?"NewValue"`) and was quadratic even when anchored.
|
||||
* **Per-pattern timeout, no per-line budget.** Each regex had a 1 s timeout, the timeout was
|
||||
swallowed at Debug, and the loop tried all ~80 active patterns for every line — one crafted
|
||||
line concatenating several trigger prefixes cost several seconds of SYSTEM CPU.
|
||||
* **Unbounded line length.** `StreamReader.ReadLineAsync` materializes the whole line before
|
||||
anyone can look at its size; a multi-hundred-MB line is an OutOfMemory in the poll loop, which
|
||||
retries from the same bookmark every second.
|
||||
|
||||
Genuine IME decision signals (app failures, enforcement states, ESP phases, user-session
|
||||
completion) queue behind that work — a monitoring blackout during the enrollment window.
|
||||
|
||||
# Design
|
||||
|
||||
## Anchored, linear pattern pack
|
||||
|
||||
* **Every shipped pattern starts with `^`.** The tracker compiles with
|
||||
`IgnoreCase | Compiled | Singleline` and no `Multiline`, so `^` is the start of the matched
|
||||
string — `CmTraceLogEntry.Message`, i.e. the text after `<![LOG[` (for multiline entries the
|
||||
assembled entry, still offset 0). .NET treats a leading `^` as a beginning anchor: a
|
||||
non-matching pattern costs O(prefix), never a scan. Evidence for the invariant: across eight
|
||||
real diagnostics sets (~9,000 entries, all six log families) every match of the old pack began
|
||||
at offset 0 — except one class of mid-line hits that were false positives (below).
|
||||
* **No chained unbounded scans.** `IME-APP-VERSION` now spells the report-delta JSON out
|
||||
(`\{(?:"OldValue":[^,}]{0,64},)?"NewValue":"`) instead of scanning `[^}]*?` at every
|
||||
occurrence. The test `ImeLogPatternPackLinearityTests` rejects `[^x]*` shapes outright.
|
||||
* **Raw lines.** A line that fails CMTrace parsing (orphaned continuation lines, lines after a
|
||||
dropped entry) is still matched raw; with `^` it matches only if it *begins* with the pattern.
|
||||
Field data: the unanchored `PS-SCRIPT-CONTEXT` fired 122 times on
|
||||
`[Win32App] SideCarScriptDetectionManager Launch powershell executor in machine session`
|
||||
(Win32 detection scripts in AppWorkload.log), each time overwriting the pending *platform*
|
||||
script's `RunContext`. Anchoring removed that contamination; it is asserted by test.
|
||||
* **Custom patterns are not rewritten.** Tenant-authored patterns (and cached pre-anchor
|
||||
configs) keep their semantics; `CompilePatterns` names the enabled patterns without `^` once
|
||||
at Info so a pass summary's budget breaks can be attributed.
|
||||
|
||||
## Bounded read loop (`CheckLogFilesAsync`)
|
||||
|
||||
* **`BoundedLineReader`** replaces `StreamReader`: byte-oriented, splits on `\n`, strips one
|
||||
`\r`, decodes UTF-8 per line, honours a BOM only at absolute file offset 0. A line beyond
|
||||
`MaxEntryBytes` (32 MB) is never materialized — the reader keeps the capped prefix, discards
|
||||
the rest while scanning for the terminator and flags `LastLineTruncated`. Its `Position` is
|
||||
the exact offset of the next unread byte, so bookmarks are exact (the StreamReader's decoder
|
||||
read-ahead used to lose up to a buffer of lines on a cancelled pass).
|
||||
* **Why 32 MB.** Real logs carry single lines of ~110 KB routinely
|
||||
(`IntuneManagementExtension.log` policy dumps) and one large tenant's `AppWorkload.log`
|
||||
writes a 5.6 MB `Get policies = […]` line that `IME-POLICIES` legitimately captures (app
|
||||
names for the dashboard). A "few KB" cap would drop genuine entries; with anchored patterns
|
||||
the CPU cost of a large line is O(n) for the handful of patterns whose prefix matches, so the
|
||||
cap only has to bound memory. The same constant caps an assembled multiline entry.
|
||||
* **Oversized lines are dropped, not matched.** Captures on a cut line would be wrong. If the
|
||||
capped prefix opens a CMTrace entry without closing it, the existing `skippingDroppedEntry`
|
||||
mechanism skips the entry's continuation lines instead of raw-matching them; a line that
|
||||
opens a *new* entry always ends the skip and is processed itself.
|
||||
* **Per-line matching budget.** `MatchLine` runs all active patterns under a 2 s budget that
|
||||
sums only the time spent inside `Regex.Match` on that line; a pattern that times out is
|
||||
skipped and counted, and once the budget is spent the remaining patterns are skipped for that
|
||||
line (matches already handled stand). No genuine line comes near it — the budget exists for
|
||||
unanchored custom patterns and hostile input. It is deliberately not the wall-clock across
|
||||
the pattern loop: that window also holds the match handlers (signal ingress, match-log
|
||||
append) and scheduler stalls, and session 946ccbd6 (2026-08-30, Hyper-V guest with 5–10 s
|
||||
process-wide stalls, 1 MB log, no line over 5 KB) broke the wall-clock budget twice on
|
||||
genuine lines and skipped their remaining patterns.
|
||||
* **Held-back tails.** An unterminated tail at EOF (a physical line without `\n`, or a
|
||||
multiline entry without `]LOG]!>`) is normally the writer mid-write. Instead of dropping it
|
||||
(and raw-matching its remainder next pass) the bookmark stays at the entry start. The hold
|
||||
lasts until the entry completes or the file has stood still for `HeldTailSettle` (1 s), and
|
||||
only for tails up to 1 MB — a never-closing hostile tail costs a handful of re-reads, then is
|
||||
processed as-is and the bookmark advances.
|
||||
* **One Warning per pass and file.** `oversizedLines`, `regexTimeouts`, `lineBudgetBreaks` and
|
||||
the first skipped pattern ID are logged in a single line after the pass when any is non-zero
|
||||
(fail-soft with a trace); the first timeout per pattern per process additionally names the
|
||||
pattern at Debug. No per-line logging, so a hostile file cannot flood the agent log.
|
||||
* **Deliberately not done: per-file preemption.** The first pass over a legitimate
|
||||
`AppWorkload.log` can be hundreds of MB and runs in one pass by design; after the changes
|
||||
above the work per byte is linear and small, so a writer can only delay processing in
|
||||
proportion to the bytes it writes.
|
||||
|
||||
## Pack hygiene against the decompiles (2026-08-30)
|
||||
|
||||
Every shipped pattern's distinctive phrase was checked in the IME decompiles 1.97.107.0,
|
||||
1.104.102.0 and 1.105.103.0 (`ime-decompiles` repo): no wording changed in any of them. The 11
|
||||
disabled legacy patterns whose literals last existed in IME 1.50 (fleet minimum 1.97) were
|
||||
deleted rather than left as re-enable traps; `IME-DO-TIMEOUT-1/2` now match the exact source
|
||||
literal (`[Win32App DO] DO downloading is not finished within timeout, jobId = …, fileId = …`)
|
||||
instead of two lookaheads; `IME-DO-TEL` has no emitter from 1.104 on and stays only while
|
||||
1.101/1.103 devices are in the fleet (`get_ime_version_history`).
|
||||
|
||||
## Observability (2026-08-30)
|
||||
|
||||
Everything the loop skips is counted (`ImeTrackerHealth`: lines read, entries matched, oversized
|
||||
lines, regex timeouts, budget breaks, held tails, unanchored patterns, files tailed, backlog
|
||||
bytes) and, together with a per-pattern match histogram, persisted in `ImeTrackerState`. The
|
||||
periodic `agent_metrics_snapshot` carries the counters as `ime_*` keys, the first pass that
|
||||
skipped work raises the one-shot `ime_tracker_degraded` Warning (immediate upload), and the
|
||||
termination handler emits `ime_pattern_hits` — the input of the backend's pattern-drift loop
|
||||
([IME Pattern Health](../backend/ime-pattern-health.md)).
|
||||
|
||||
# Verification
|
||||
|
||||
* `ImeLogPatternPackLinearityTests` loads the shipped pack, asserts the `^` invariant, runs four
|
||||
hostile constructions (repeated prefix, the reported `"DetectedIdentityVersion":{x`
|
||||
repetition, long word runs, prefix+keywords with newlines; 512 KB each) against every pattern
|
||||
with the agent's exact regex options and fails on a timeout or > 150 ms (best of three),
|
||||
and re-matches representative real lines with their captures.
|
||||
* `BoundedLineReaderTests`, `ImeLogTrackerReadLoopHardeningTests` (oversized single line,
|
||||
oversized multiline opener, held-back tail, settle, cancelled pass bookmark, single Warning).
|
||||
* Parity: the old and new packs were run over eight real diagnostics sets with the tracker's
|
||||
assembly semantics; the match sets are identical apart from the removed `PS-SCRIPT-CONTEXT`
|
||||
false positives. `--run-ime-matching` (the agent's offline matcher) now assembles multiline
|
||||
entries like the tracker so it reproduces the same result.
|
||||
|
||||
# Citations
|
||||
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeLogTracker.LogProcessing.cs` — `CheckLogFilesAsync`, `MatchLine`, `HoldBackTail`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/BoundedLineReader.cs`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeLogTracker.cs` — `MaxEntryBytes`, `HeldTailSettle`, `CompilePatterns`
|
||||
* `rules/ime-log-patterns/*.json`, `rules/scripts/combine.js`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Monitoring/Ime/ImeLogPatternPackLinearityTests.cs`
|
||||
* [CMTrace Time Resolution](cmtrace-time-resolution.md) — the linear CMTrace line parser this loop feeds
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
type: concept
|
||||
title: IME Process Watcher — Identity Before Attach, Re-Arm After Exit
|
||||
description: Why ImeProcessWatcher attaches only to a process that runs in session 0 with its image under the IME install root — a process name is not an identity — and why a reported exit re-arms discovery instead of ending it.
|
||||
resource: src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeProcessWatcher.cs
|
||||
tags: [agent, ime, process, security, cwe-807, reliability]
|
||||
timestamp: 2026-08-30
|
||||
---
|
||||
|
||||
# Problem
|
||||
|
||||
`ImeProcessWatcher` reports `ime_process_exited` (Warning — read as an IME crash indicator in the
|
||||
timeline) by subscribing to `Process.Exited` of `IntuneManagementExtension.exe`. Until 2026-08-30 it
|
||||
attached to `Process.GetProcessesByName(...)[0]` and stopped discovery for good once attached.
|
||||
|
||||
A process name is not a protected namespace. During AccountSetup (and on Device Preparation flows)
|
||||
a standard-user session is live and can start any binary renamed to `IntuneManagementExtension.exe`.
|
||||
Two consequences (CWE-807 — the same class as the agent instance guard and the local-admin dynamic
|
||||
allowance):
|
||||
|
||||
* **Forged signal** — the fake exits, the SYSTEM agent emits a Warning with the fake's PID/exit
|
||||
code, and failure diagnosis is misdirected.
|
||||
* **Muted signal** — attached to the fake, discovery is stopped; the real service is never watched,
|
||||
and a genuine IME crash later in the enrollment is never reported. Even without an attacker the
|
||||
one-shot design missed every IME exit after the first (an IME service restart is not rare).
|
||||
|
||||
# Schema
|
||||
|
||||
`ImeProcessIdentity` establishes identity by facts a standard user cannot forge:
|
||||
|
||||
| Check | Source | Why it holds |
|
||||
|---|---|---|
|
||||
| `SessionId == 0` | `Process.SessionId` | Only services and their children run in session 0; a user session cannot start a process there. |
|
||||
| image under `%ProgramFiles(x86)%\Microsoft Intune Management Extension\` (also `%ProgramW6432%`/`%ProgramFiles%` variants) | `QueryFullProcessImageName` (cross-bitness — IME is 32-bit, the agent may be 64-bit; `MainModule` throws there), `MainModule.FileName` fallback | Program Files is admin-writable only. Segment-aware prefix (`...Extension\`), `GetFullPath`-normalised, so a sibling folder or `..` does not pass. |
|
||||
| file name exactly `IntuneManagementExtension.exe` | image path | `GetProcessesByName` matches the name without extension. |
|
||||
|
||||
Unknown facts (session `-1`, unresolved path) are **untrusted** — a false negative only delays
|
||||
attach by one 5 s discovery tick, a false positive mutes the signal for the session.
|
||||
|
||||
Among trusted candidates the **oldest** by `StartTime` wins (the SCM-started service predates
|
||||
anything transient). Untrusted name matches are logged once per PID as a Warning in the agent log
|
||||
(`ignoring untrusted IntuneManagementExtension.exe candidate (PID, session, image)`) and never
|
||||
attached; no timeline event is raised for them.
|
||||
|
||||
**Re-arm:** `OnImeExited` detaches, emits the event, and restarts the discovery timer. Only the
|
||||
currently attached `Process` object is honoured (`ReferenceEquals`), so a late `Exited` from a
|
||||
replaced or disposed handle cannot produce a duplicate event. A process that exits between probe
|
||||
and subscription is handled by an explicit `HasExited` check after subscribing.
|
||||
|
||||
The watcher owns the `Process` handles its source returns (like `GetProcessesByName`) and disposes
|
||||
them — tests must hand it fresh handles (`Process.GetProcessById`), not the ones they hold.
|
||||
|
||||
# Examples
|
||||
|
||||
* Fake started in session 1 from `Downloads\IntuneManagementExtension.exe` before the service is up →
|
||||
never attached; its exit emits nothing; the service arriving later is attached normally.
|
||||
* Service exits (crash or restart) → `ime_process_exited` (PID, exit code, uptime), discovery
|
||||
re-armed, the restarted service is attached on the next tick and its later exit is reported too.
|
||||
* Fake and service both running, fake listed first → the service is attached.
|
||||
|
||||
Covered by `ImeProcessWatcherIdentityTests` (pure identity table + stand-in `cmd.exe` processes
|
||||
with injected identity facts).
|
||||
|
||||
# Citations
|
||||
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeProcessIdentity.cs`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeProcessWatcher.cs`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Monitoring/Ime/ImeProcessWatcherIdentityTests.cs`
|
||||
* Related: [Local Admin Analyzer](local-admin-analyzer.md) (CWE-807 dynamic allowance).
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
type: Concept
|
||||
title: Local Admin Analyzer — account inventory, dormant accounts and Administrators membership
|
||||
description: What the local_admin_analysis event asserts about a device at enrollment start and completion, why disabled accounts are inventoried (a /active:no backdoor re-enabled after enrollment is dormant at both scans), how Administrators-group membership is read from the SAM by well-known SID (NetLocalGroupGetMembers, independent of WMI and account state), why built-ins are matched by well-known RID (localized Guest/Administrator), why the shutdown-time dynamic allowance never covers a local SAM account (CWE-807 — the login state is attacker-controlled), which folders are not profiles, which members are scored versus only listed, and why the built-in Administrator's enabled state is reported but not scored.
|
||||
resource: /src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/LocalAdminAnalyzer.cs
|
||||
tags:
|
||||
- agent
|
||||
- security
|
||||
- local-admin
|
||||
- analyze-rules
|
||||
- bypass-detection
|
||||
timestamp: 2026-08-30T00:00:00+02:00
|
||||
---
|
||||
|
||||
# Local Admin Analyzer
|
||||
|
||||
`LocalAdminAnalyzer` emits one `local_admin_analysis` event at agent startup and one at
|
||||
shutdown. The pair is a before/after inventory of local accounts on the device; the backend
|
||||
rule `ANALYZE-ID-002` alerts when the finding is `unexpected_local_admins_detected`.
|
||||
The threat model is the classic Autopilot bypass: a person with a Shift+F10 SYSTEM console
|
||||
during OOBE/ESP who creates a persistent local administrator.
|
||||
|
||||
# Schema
|
||||
|
||||
## Checks
|
||||
|
||||
| Check | Source | What it yields |
|
||||
|---|---|---|
|
||||
| `bypass_nro` | `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\BypassNRO` | value + `flagged` |
|
||||
| Local accounts | WMI `Win32_UserAccount WHERE LocalAccount = True` (Name, Disabled, SID) | `accounts_checked`, `account_details` (name / disabled / administrators_member), `unexpected_accounts`, `builtin_administrator_enabled` |
|
||||
| Administrators membership | `NetLocalGroupGetMembers` level 2 on the group resolved from `S-1-5-32-544` (`LocalGroupNativeMethods`) | `administrators_group.{enumerated,error_code,members}`, `unexpected_admin_members` |
|
||||
| Profile folders | `C:\Users` top-level directories, junctions and `$<32 hex>` folders skipped | `profiles_found`, `unexpected_profiles` |
|
||||
|
||||
## Allowed list
|
||||
|
||||
Built-in names (`Administrator`, `Guest`, `DefaultAccount`, `WDAGUtilityAccount`,
|
||||
`defaultuser0/1/2`, `kioskUser0` — the Assigned Access autologon account of kiosk profiles —
|
||||
and the non-account folders `Public`, `Default`, `Default User`, `All Users`) plus the
|
||||
tenant's *Allowed Local Accounts* (glob `*`/`?`, case-insensitive).
|
||||
`defaultuser1/2` stay allowlisted deliberately: they do appear during OOBE enrollment.
|
||||
|
||||
**Well-known RIDs.** Built-in accounts are additionally recognised by RID on the machine SID
|
||||
(`S-1-5-21-…-500` Administrator, `-501` Guest, `-503` DefaultAccount, `-504`
|
||||
WDAGUtilityAccount), for the WMI inventory and for the SAM-member fallback alike. Localized
|
||||
installs name them `Gast`, `Invité`, `Гость`, `Administrateur`, … — disabled by default and
|
||||
therefore invisible before disabled accounts were inventoried; without the RID match every
|
||||
non-English device would flag its own Guest. Name matching is unchanged (an ordinary-RID
|
||||
account named `Gast` is still unexpected).
|
||||
|
||||
## Dynamic allowance at shutdown
|
||||
|
||||
The shutdown run adds the logged-in users (explorer.exe owners outside session 0) to the
|
||||
allowed list — but **only names that are not a local SAM account**. The WMI inventory is read
|
||||
first; a logged-in name found in it is rejected for both the account and the profile check
|
||||
and reported as `logged_in_local_accounts` (Warning in the agent log).
|
||||
|
||||
Why this is safe for false positives: in real sessions the logged-in identity is an Entra or
|
||||
domain user (user-driven, hybrid, or an Entra user signing in to a self-deploying device
|
||||
after ESP). Those never appear in `Win32_UserAccount`; the allowance only ever mattered for
|
||||
their `C:\Users` folder — and `unexpected_profiles` alone scores 0. A logged-in *local*
|
||||
account is the one case the allowance changed the score, and that is the attack: an account
|
||||
created after the startup scan that signs itself in before the shutdown scan (CWE-807 —
|
||||
exempting on a condition the adversary controls). Kiosk autologon (`kioskUser0`) is covered
|
||||
by the built-in list instead.
|
||||
|
||||
## Profile folders that are not profiles
|
||||
|
||||
Junctions (`All Users`, `Default User` and localized variants such as `Все пользователи`,
|
||||
all pointing at ProgramData / Default) and `$<32 hex>` folders are skipped entirely — neither
|
||||
can be the profile of a local account (a SAM account name is at most 20 characters), so they
|
||||
can never contribute to the profile-overlap score; listing them was payload noise.
|
||||
|
||||
## Evaluation (`EvaluateAccounts`, pure)
|
||||
|
||||
* Every local account is evaluated, **disabled ones included**; the disabled state is reported
|
||||
per account instead of excluding the account.
|
||||
* An account is an Administrators member when its SID matches a group member, or — SID
|
||||
unavailable — its name matches a member of the local machine domain.
|
||||
* `unexpected_accounts` = accounts not matching the allowed list (any state).
|
||||
* `unexpected_admin_members` = unexpected accounts holding Administrators membership, plus
|
||||
local-domain members not on the allowed list that WMI did not return at all (WMI failure
|
||||
or provider gap — the SAM is authoritative for membership).
|
||||
* Members outside the machine domain (Entra role SIDs such as `S-1-12-1-…` on Entra-joined
|
||||
devices, domain groups on hybrid devices, unresolved/deleted SIDs) are **listed for delta
|
||||
comparison but never scored** — they are expected on joined devices.
|
||||
* `builtin_administrator_enabled` = `!Disabled` of the RID-500 account (null when absent).
|
||||
Reported, not scored: whether OOBE enables it transiently is not established; the field
|
||||
collects evidence first.
|
||||
|
||||
## Scoring
|
||||
|
||||
| Signal | Weight |
|
||||
|---|---|
|
||||
| `BypassNRO = 1` | +20 |
|
||||
| any `unexpected_accounts` | +40 |
|
||||
| any `unexpected_admin_members` | +40 |
|
||||
| unexpected account with a matching `C:\Users` folder | +40 |
|
||||
|
||||
Capped at 100. `0` → Info `no_unexpected_admins_detected`; `<40` → Info
|
||||
`bypass_nro_flag_only`; `40–79` → Warning, `≥80` → Error, both
|
||||
`unexpected_local_admins_detected`. A dormant backdoor with admin membership therefore lands
|
||||
at 80 (Error) without ever having a profile folder.
|
||||
|
||||
## Failure behaviour
|
||||
|
||||
WMI and the SAM read are independent; either failing is logged at Warning with the native
|
||||
status (`NET_API_STATUS=…`) and the other source still evaluates. The event carries
|
||||
`administrators_group.enumerated=false` + `error_code` so a "no members" result is never
|
||||
mistaken for an empty group.
|
||||
|
||||
# Examples
|
||||
|
||||
`net user backdoor P@ss /add /active:no` + `net localgroup Administrators backdoor /add`
|
||||
during OOBE, re-enabled by a scheduled task after enrollment: at both scans the account is
|
||||
disabled; before this change it was skipped entirely. Now `unexpected_accounts=[backdoor]`,
|
||||
`unexpected_admin_members=[backdoor]`, `account_details[backdoor].disabled=true` →
|
||||
confidence 80, Error.
|
||||
|
||||
`net user backdoor P@ss /add` + `net localgroup Administrators backdoor /add` during ESP,
|
||||
then an interactive sign-in as `backdoor` (fast user switching) before enrollment completes:
|
||||
the shutdown scan finds `backdoor` in the SAM inventory → not allowed dynamically →
|
||||
`unexpected_accounts=[backdoor]`, `unexpected_admin_members=[backdoor]`, profile overlap →
|
||||
confidence 100, Error, `logged_in_local_accounts=[backdoor]`. Before this change the login
|
||||
alone silenced the finding to 0.
|
||||
|
||||
German device, tenant allowed list empty: `Gast` (RID 501, disabled) and `Administrator`
|
||||
are inventoried and matched by RID → `unexpected_accounts=[]`, confidence 0.
|
||||
|
||||
`net localgroup Administrators adm-helpdesk /add` where `adm-*` is allowlisted: not flagged
|
||||
(the allowed list is the tenant's statement of intent), but `administrators_group.members`
|
||||
shows the new membership in the shutdown event for delta review.
|
||||
|
||||
# Citations
|
||||
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Analyzers/LocalAdminAnalyzer.cs`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Interop/LocalGroupNativeMethods.cs`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Monitoring/Analyzers/LocalAdminAnalyzerEvaluateAccountsTests.cs`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Monitoring/Analyzers/LocalAdminAnalyzerDynamicAllowanceTests.cs`
|
||||
* `rules/analyze/ANALYZE-ID-002.json`
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
type: Concept
|
||||
title: Admin Identity Binding — tid + oid behind every cross-tenant-role UPN
|
||||
description: Why a GlobalAdmins or DelegatedAdmins row keyed on a UPN string must never confer access by itself in an API that accepts tokens from every Entra tenant, the AdminIdentityBindings table that pins each such UPN to its immutable home tenant id and object id, the fail-closed resolution order in both role services, the first-sign-in object-id pin, and the explicit rebind surface.
|
||||
description: Why a GlobalAdmins, DelegatedAdmins or McpUsers row keyed on a UPN string must never confer access by itself in an API that accepts tokens from every Entra tenant, the AdminIdentityBindings table that pins each such UPN to its immutable home tenant id and object id, the fail-closed resolution order in the role services and the MCP whitelist, the first-sign-in object-id pin, and the explicit rebind surface.
|
||||
resource: src/Backend/AutopilotMonitor.Functions/Services/AdminIdentityBindingService.cs
|
||||
tags:
|
||||
- backend
|
||||
@@ -10,7 +10,8 @@ tags:
|
||||
- multi-tenant
|
||||
- global-admin
|
||||
- delegated-admin
|
||||
timestamp: 2026-08-29T00:00:00+02:00
|
||||
- mcp
|
||||
timestamp: 2026-08-30T00:00:00+02:00
|
||||
---
|
||||
|
||||
# Problem
|
||||
@@ -83,10 +84,28 @@ winner). Two accounts racing the first sign-in inside the home tenant therefore
|
||||
Row lookups stay cached by UPN; the binding has its own 30 s cache keyed by UPN and is invalidated on
|
||||
pin, rebind and removal. A rebind on one scaled-out instance converges on the others within seconds.
|
||||
|
||||
## The MCP whitelist is a third UPN-keyed grant
|
||||
|
||||
`McpUsers` (PK `McpUsers`, RK = lowercase UPN) is the WhitelistOnly allow-list consulted by
|
||||
`GET auth/mcp`, which runs under the AuthenticatedUser policy — a token from any Entra tenant reaches it.
|
||||
Before 2026-08-30 the row was honoured by UPN alone, so a same-UPN identity from another tenant
|
||||
received `allowed=true` / `accessGrant=McpUser`, inherited the row's `UsagePlan` (a paid quota tier
|
||||
provisioned for another organization's user), and was also hit by the row's Disabled kill-switch. The
|
||||
data reach stayed tenant-bound (the MCP server routes a non-global caller on its own JWT `tid`), but the
|
||||
GA-curated whitelist and the per-user entitlement were bypassable.
|
||||
|
||||
`McpUserService` now resolves the whitelist exactly like the role services: row by UPN (cached 30 s) →
|
||||
no row ⇒ `NotPresent` without touching the binding → `IsBoundAsync(identity)` false ⇒ `NotPresent`. Grant,
|
||||
usage-plan override (`GetBoundMcpUserAsync`, used by `McpQuotaService.ResolvePlanAsync` and the
|
||||
self-service usage view) and the Disabled kill-switch all belong to the bound identity only; an unbound
|
||||
legacy row is inert. `POST global/mcp-users` binds first via the same resolver / 422 / 409 contract as the
|
||||
other grant endpoints, and the MCP users settings section shows the same home-tenant picker on 422. The
|
||||
AllMembers path is unaffected — it is tenant-bound by the auth middleware itself.
|
||||
|
||||
## Grants bind first — and resolve the identity themselves
|
||||
|
||||
Granting is "type the UPN". The three grant endpoints (`POST auth/global-admins`,
|
||||
`POST global/delegated-admins`, `POST global/tenant-groups/{id}/assignees`) accept `homeTenantId` /
|
||||
Granting is "type the UPN". The four grant endpoints (`POST auth/global-admins`,
|
||||
`POST global/delegated-admins`, `POST global/tenant-groups/{id}/assignees`, `POST global/mcp-users`) accept `homeTenantId` /
|
||||
`objectId` only as optional overrides; normally `AdminIdentityResolver` supplies them:
|
||||
|
||||
1. **Sign-in history** (`UserActivity`, filtered on the UPN): the person has signed in before ⇒ tid + oid
|
||||
@@ -98,7 +117,7 @@ Granting is "type the UPN". The three grant endpoints (`POST auth/global-admins`
|
||||
tenants) and retries with `homeTenantId`. This is the only manual step, and only for a UPN whose
|
||||
domain the platform has never seen.
|
||||
|
||||
`AddGlobalAdminAsync`, `DelegatedAdminService.UpsertAsync` and `AssignGroupAsync` then take the resolved
|
||||
`AddGlobalAdminAsync`, `DelegatedAdminService.UpsertAsync`, `AssignGroupAsync` and `AddMcpUserAsync` then take the resolved
|
||||
`homeTenantId` / `objectId` and call `EnsureBoundAsync` **before** writing the role row. `EnsureBoundAsync` creates the binding, keeps a compatible one (same tenant; the
|
||||
supplied object id equals the pin or upgrades an unpinned one), and throws
|
||||
`IdentityBindingConflictException` for a different tenant or a different pinned object id — the
|
||||
@@ -158,7 +177,7 @@ Same row, binding without an object id: the first `tid=1111…` sign-in pins its
|
||||
* `src/Backend/AutopilotMonitor.Functions/Services/AdminIdentityBindingService.cs` — verdict, pin, grant-time binding, rebind; the table entity.
|
||||
* `src/Backend/AutopilotMonitor.Functions/Services/AdminIdentityResolver.cs` — sign-in-history / UPN-domain resolution at grant time (`AdminIdentityResolverTests`).
|
||||
* `src/Web/autopilot-monitor-web/app/admin/components/IdentityBindingsSection.tsx` — the per-tenant inspect/correct panel.
|
||||
* `src/Backend/AutopilotMonitor.Functions/Services/GlobalAdminService.cs`, `DelegatedAdminService.cs` — resolution order.
|
||||
* `src/Backend/AutopilotMonitor.Functions/Services/GlobalAdminService.cs`, `DelegatedAdminService.cs`, `McpUserService.cs` — resolution order (`McpUserServiceIdentityBindingTests` for the whitelist matrix).
|
||||
* `src/Backend/AutopilotMonitor.Functions/DataAccess/TableStorage/TableAdminRepository.cs` — `TryPinIdentityObjectIdAsync` and the binding CRUD.
|
||||
* `src/Backend/AutopilotMonitor.Functions/Functions/Admin/IdentityBindingManagementFunction.cs` — the rebind surface and the shared `IdentityBindingRequest.Validate`.
|
||||
* `src/Backend/AutopilotMonitor.Functions.Tests/AdminIdentityBindingServiceTests.cs`, `AdminIdentityBindingAuthorizationTests.cs` — verdict matrix, race semantics, and the middleware-level denials for foreign-tenant and recycled-UPN tokens.
|
||||
|
||||
@@ -9,7 +9,7 @@ tags:
|
||||
- archive
|
||||
- blob
|
||||
- queue
|
||||
timestamp: 2026-08-29T17:30:00+02:00
|
||||
timestamp: 2026-08-30T12:00:00+02:00
|
||||
---
|
||||
|
||||
# IME Installer Archive
|
||||
@@ -22,7 +22,7 @@ on. Two stores:
|
||||
| Store | Content | Written by |
|
||||
| --- | --- | --- |
|
||||
| Blob container `ime-archive` (private, kept by design, not tenant-scoped) | `{version}/IntuneWindowsAgent.msi` (write-once) + `{version}/provenance.json` (derived, overwritable) | `ImeMsiArchiver` via the `ime-msi-archive` queue; `/ime-decompile` skill as manual backfill |
|
||||
| Table `ImeVersionHistory` (partition `Global`, RowKey = version) | first/last seen, session count, `MsiArchiveStatus`, `MsiArchiveUpdatedAt`, `MsiArchiveBlobPath`, `MsiSha256`, `MsiBytes`, `MsiSourceUrl` | `RecordImeVersionAsync` (sighting), `UpdateImeVersionArchiveInfoAsync` (archive outcome / queued stamp) |
|
||||
| Table `ImeVersionHistory` (partition `Global`, RowKey = version) | first/last seen, session count, `CorroboratedAt`, `MsiArchiveStatus`, `MsiArchiveUpdatedAt`, `MsiArchiveBlobPath`, `MsiSha256`, `MsiBytes`, `MsiSourceUrl` | `RecordImeVersionAsync` (sighting), `UpdateImeVersionArchiveInfoAsync` (archive outcome / queued stamp) |
|
||||
|
||||
# Schema
|
||||
|
||||
@@ -45,6 +45,25 @@ older than 24 h (`RequeueBackoff`) or never happened. The ingest path then merge
|
||||
sessions of that version see "in flight" and do not pile on. Worst case for a version no
|
||||
host serves any more: one walk over the hosts per day, not one per enrollment.
|
||||
|
||||
## Trust boundary of a sighting
|
||||
|
||||
The version string is **device-supplied** (any device holding a tenant agent credential can
|
||||
send an `ime_agent_version` event) and becomes the RowKey of a caller-independent GLOBAL
|
||||
partition that every tenant reads. Four layers keep one tenant's device from turning a
|
||||
claim into a platform fact:
|
||||
|
||||
| Layer | Where | Effect |
|
||||
| --- | --- | --- |
|
||||
| Plausibility guard | `ImeMsiArchiver.IsPlausibleVersion`, called first in `RecordImeVersionAsync` (and again by the archiver) | plain dotted digits, 2–4 components, inside Windows Installer `ProductVersion` bounds (major/minor ≤ 255, build ≤ 65535). Anything else → `ImeVersionSighting.Rejected`: no row, no ops event, no archive job, no re-queue. |
|
||||
| One sighting per session | `UpdateSessionImeAgentVersionAsync` returns whether the session's stored version changed; only then does the ingest path call `RecordImeVersionAsync` | `SessionCount` moves once per session and version; a re-sent or replayed event, a tombstoned or unregistered session, count nothing. |
|
||||
| Corroboration | merge path of `RecordImeVersionAsync` stamps `CorroboratedAt` on the first sighting from a tenant other than `FirstSeenTenantId` | the first-seen tenant's own devices can never corroborate their own claim. |
|
||||
| Fleet confirmation on read | `GetImeVersionHistoryFunction.IsFleetConfirmed` (MemberRead projection only) | tenant members see a version once it is `Archived` (ProductVersion matched a real package), or corroborated, or first seen before `LegacyTrustCutoffUtc` (2026-08-31 — the 16 rows reviewed on 2026-08-30 are all genuine builds). Global scope sees every row, unconfirmed ones included. |
|
||||
|
||||
What remains possible for a hostile device: a plausible, never-shipped version creates a
|
||||
row visible to Global Admins plus one `NewImeVersionDetected` ops event and one archive walk
|
||||
that ends `Failed:VersionMismatch` (no blob). It is never listed to other tenants and can
|
||||
never occupy the archive folder of a real build.
|
||||
|
||||
## Candidate hosts and the version check
|
||||
|
||||
Microsoft distributes IME from several versionless URLs that carry **different rollout
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
type: concept
|
||||
title: IME Pattern Health — the pattern-drift loop
|
||||
description: How a Microsoft change to IME log wording becomes visible before it breaks timelines — the agent's session-end pattern histogram, the global per-IME-version statistics, the data-driven drift alarm, and the operator page/MCP tool that turn the alarm into a repair workflow.
|
||||
resource: src/Backend/AutopilotMonitor.Functions/Services/Ime
|
||||
tags: [backend, agent, ime, log-patterns, drift, ops-events, metrics, mcp]
|
||||
timestamp: 2026-08-30
|
||||
---
|
||||
|
||||
# Problem
|
||||
|
||||
The IME log-pattern pack is the product's core signal source, and Microsoft changes IME log
|
||||
wording without notice. Until now a changed line was invisible until sessions looked wrong:
|
||||
the agent kept matching nothing, the client log said nothing, and the only alarm was the
|
||||
registry/log reconciliation for app states. Tracker problems (skipped lines, regex timeouts)
|
||||
were a client-log Warning nobody sees without pulling diagnostics.
|
||||
|
||||
# Design
|
||||
|
||||
Three layers, one loop:
|
||||
|
||||
## 1. Tracker health in `agent_metrics_snapshot`
|
||||
|
||||
`ImeLogTracker` keeps cumulative counters (`ImeTrackerHealth`: lines read, entries matched,
|
||||
oversized lines, regex timeouts, per-line budget breaks, held tails, unanchored patterns,
|
||||
files tailed, backlog bytes = Σ file length − bookmark). `AgentSelfMetricsCollector` reads them
|
||||
through a read-only probe (`ImeLogHost.GetTrackerHealth`) and adds `ime_*` keys to the periodic
|
||||
snapshot next to the spool queue. Fleet expectation for every skip counter: **0**. The session
|
||||
page's "IME Tracker" section shows queue depth and skips.
|
||||
|
||||
## 2. `ime_tracker_degraded` (Warning, one-shot, immediate upload)
|
||||
|
||||
The first pass that skipped work raises `ImeLogTracker.OnTrackerDegraded`; the adapter emits
|
||||
the event with the counters, file and first skipped pattern. Persisted flag (`ImeTrackerState`)
|
||||
so a restart cannot re-emit. Listed in the MCP `health_events` catalog; informational for the
|
||||
idle/stall clocks (`SignalActivityClassifier`).
|
||||
|
||||
## 3. `ime_pattern_hits` → `ImePatternStats` → `ImePatternDriftSuspected`
|
||||
|
||||
* **Agent.** At termination (same slot as `app_tracking_summary`, so WhiteGlove Part 2 reports
|
||||
the whole session via the persisted histogram) the handler emits `ime_pattern_hits`:
|
||||
`hits` = every ENABLED pattern ID → match count, zeros included, plus the health counters and
|
||||
`imeVersion` (IME's own "Agent version is:" line). Only sessions with a terminal run report —
|
||||
crashes/kills are outside the denominator by construction. Severity is Debug: the event is an
|
||||
operator/backend signal, hidden from the default session timeline; the session page's
|
||||
"IME Tracker" card and the ingest fold read it regardless of severity.
|
||||
* **Ingest.** `EventIngestProcessor` hands the event to `ImePatternHealthService` fire-and-forget:
|
||||
IDs are filtered against `BuiltInImeLogPatterns.BuiltInPatternIds` (a device may only claim an
|
||||
ID; tenant custom IDs never reach the global table), the version must pass
|
||||
`ImeMsiArchiver.IsPlausibleVersion` (event value first, session row as fallback), then one
|
||||
partition read + one transactional batch upsert into `ImePatternStats`
|
||||
(PK = version, RK = patternId: `Sessions`, `SessionsWithHit`, `Hits`, `LastHitAt`,
|
||||
`DriftFlaggedAt`). Permanent table (`TableLifecycleBucketTests` KeptByDesign) — the baseline
|
||||
needs the established versions.
|
||||
* **Drift statistic (`ImePatternDriftEvaluator`, pure).** No hard-coded must-hit list.
|
||||
Baseline = the version with the most reporting sessions (≥ 100, never the candidate).
|
||||
A pattern is *expected* when its baseline hit rate (`SessionsWithHit / Sessions`) is ≥ 0.8.
|
||||
Drift is suspected when the candidate has ≥ 25 reporting sessions and an expected pattern
|
||||
matched in none of them. Conditional patterns (platform/remediation scripts, WinGet, errors)
|
||||
sit below 0.8 on the baseline and cannot alarm. One alarm per version × pattern:
|
||||
`TryMarkImePatternDriftFlaggedAsync` (ETag-conditional) then
|
||||
`OpsEventService.RecordImePatternDriftSuspectedAsync` (Warning, Agent, `System.Ingest`;
|
||||
dual-registered in the web `OPS_EVENT_TYPES`). The stats snapshot used for evaluation is
|
||||
cached 10 minutes and patched with each batch, so a version crossing the threshold is judged
|
||||
promptly.
|
||||
* **Read side.** `GET metrics/ime-pattern-health` (`GlobalReadOrAdmin`) returns baseline,
|
||||
thresholds, versions (with `ImeVersionHistory` first/last seen and fleet session count),
|
||||
patterns (catalog + retired IDs still in the statistics), cells and open alerts. Web:
|
||||
Global Admin → Metrics → IME Pattern Health (matrix, legend: drift / silent / low / few).
|
||||
MCP: `get_ime_pattern_health` (GA + Global Reader).
|
||||
|
||||
# Workflow on an alert
|
||||
|
||||
1. `ImePatternDriftSuspected` names version + pattern; the matrix shows the cell.
|
||||
2. `search_sessions imeAgentVersion=<version>` → `get_session_diagnostics` on a session with a
|
||||
package (upload mode must not be Off).
|
||||
3. `/ime-pattern-validate` against the real log; `/ime-decompile` for the version's sources.
|
||||
4. Fix the pattern under `rules/ime-log-patterns/` (anchored, linear —
|
||||
[IME Log Tracker](../agent/ime-log-tracker-matching-budget.md)); Backend-Deploy delivers it
|
||||
through the config channel without an agent release.
|
||||
|
||||
# Limits
|
||||
|
||||
* Denominator = sessions that reached a terminal run; a version that only crashes never gets
|
||||
a histogram and therefore never alarms here (it alarms elsewhere).
|
||||
* The baseline is fleet-wide: a pattern that only some tenants can produce stays "conditional"
|
||||
even if it is 100 % within those tenants.
|
||||
* Thresholds are constants (`ImePatternDriftEvaluator`), tuned for the current fleet size
|
||||
(~25k sessions/month); revisit when versions roll out faster than 25 sessions per day.
|
||||
|
||||
# Citations
|
||||
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Enrollment/Ime/ImeTrackerHealth.cs`, `ImeLogTracker.cs` (`GetHealthSnapshot`, `OnTrackerDegraded`), `ImeTrackerStatePersistence.cs`
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Termination/EnrollmentTerminationHandler.cs` (`EmitImePatternHits`)
|
||||
* `src/Agent/AutopilotMonitor.Agent.V2.Core/Monitoring/Telemetry/Periodic/AgentSelfMetricsCollector.cs`
|
||||
* `src/Backend/AutopilotMonitor.Functions/Services/Ime/{ImePatternHealthService,ImePatternDriftEvaluator}.cs`
|
||||
* `src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.ImePatternStats.cs`
|
||||
* `src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetImePatternHealthFunction.cs`
|
||||
* `src/Web/autopilot-monitor-web/app/admin/metrics/sections/SectionImePatternHealth.tsx`, `imePatternHealthLogic.ts`
|
||||
* `src/McpServer/autopilot-monitor-mcp/src/tools/sessions.ts` (`get_ime_pattern_health`)
|
||||
@@ -36,6 +36,14 @@ same table) — the only reliable address: it is typed by the user on the activa
|
||||
page (`PUT /api/preview/notification-email`). The signup admin UPN may have no mailbox and
|
||||
`ContactEmail` is merely seeded from the notification email.
|
||||
|
||||
The PUT is `AuthenticatedUserWithRole`, not a member tier, because the signup user is still
|
||||
roleless until approval auto-promotes them to Admin. The function re-gates on the resolved
|
||||
role (`PreviewWhitelistFunction.MayWriteNotificationEmailAsync`): a member role or platform
|
||||
scope always may write; a roleless caller may write only while the tenant has **no enabled
|
||||
member** at all. Once any member exists the window is closed for good — the address feeds the
|
||||
welcome and farewell mails and is shown to operators as the tenant contact, so an ordinary
|
||||
employee with a JWT but no product role must not be able to redirect or clear it (403).
|
||||
|
||||
That makes activation and address entry concurrent writers. With auto-approve, activation
|
||||
usually wins (observed in production: approve at T+64 s, address saved at T+69 s → the
|
||||
approval path found no address and the mail was silently skipped).
|
||||
@@ -107,6 +115,13 @@ properties `Html`, `UpdatedBy`, `UpdatedUtc`), cached 5 minutes, invalidated on
|
||||
|
||||
* The only placeholder is `{{domainName}}`; an empty domain renders as "your organization",
|
||||
matching the built-ins. Subjects are not editable (pinned constants).
|
||||
* The domain is HTML-encoded at the point of substitution (`EmailTemplateService.Render` and the
|
||||
built-in template builders) — the mail goes out under the product's signed sender to a
|
||||
tenant-chosen recipient, so no writer of `DomainName` may ever inject markup into it.
|
||||
`DomainName` itself is tenant identity: seeded once at first login from the UPN domain
|
||||
(`AuthFunction`, only when it passes `TenantConfigValidation.IsValidDomainName` — a strict
|
||||
dotted host name), preserved from the stored row on the full-model config PUT for every
|
||||
caller, and "never writable" in the field-patch flow.
|
||||
* Limit 30 000 characters (Table string property cap is 32K UTF-16 chars).
|
||||
* The send path is fail-soft: a storage error falls back to the built-in body. The admin
|
||||
read path is not — a broken store surfaces as an error instead of silently showing the
|
||||
|
||||
+7
-3
@@ -19,7 +19,7 @@ https://docs.autopilotmonitor.com (separate repository).
|
||||
|
||||
# Agent
|
||||
|
||||
* [V2 Agent](agent/index.md) - How the on-device agent works: [runtime overview](agent/overview.md), [decision engine](agent/decision-engine.md) (reducer, signals, completion arms), [logs & persistence](agent/logs-and-persistence.md) (signal log, snapshot, crash recovery), [diagnostics package](agent/diagnostics-package.md) (built-in section catalog shared with the portal, RealmJoin/Device Preparation collection gates, caps, manifest), [Hello wizard un-skip](agent/hello-wizard-unskip.md) (why a single "WHfB disabled" read never decides completion), [Autopilot ZTD diagnostics](agent/autopilot-ztd-diagnostics.md) (event IDs, diagnostic registry, endpoints, error-code map).
|
||||
* [V2 Agent](agent/index.md) - How the on-device agent works: [runtime overview](agent/overview.md), [decision engine](agent/decision-engine.md) (reducer, signals, completion arms), [logs & persistence](agent/logs-and-persistence.md) (signal log, snapshot, crash recovery), [diagnostics package](agent/diagnostics-package.md) (built-in section catalog shared with the portal, RealmJoin/Device Preparation collection gates, handle-validated reads against the junction race, caps, manifest), [Hello wizard un-skip](agent/hello-wizard-unskip.md) (why a single "WHfB disabled" read never decides completion), [Autopilot ZTD diagnostics](agent/autopilot-ztd-diagnostics.md) (event IDs, diagnostic registry, endpoints, error-code map).
|
||||
* [Build Counter Blob](agent/build-counter-blob.md) - How agent build numbers are minted: shared counter blob with ETag-CAS so local and CI builds never collide; CI additionally attests build provenance (Sigstore).
|
||||
* [Agent Endpoint Migration](agent/endpoint-migration.md) - How the backend re-homes agents to a new API base URL via the config channel (backend move or per-tenant region move): live-fetch-only, allowlist-validated on both sides, one hop, kill wins.
|
||||
* [Gather Rule Debug Log](agent/gather-rule-debug-log.md) - EnableGatherRuleDebugLog (ConfigVersion 36): the per-tenant local trace file that explains every gather-rule evaluation decision — scope skips, on_change suppression, empty collector results, logparser position/parse-mode outcomes — for rules that produce no timeline events.
|
||||
@@ -28,9 +28,13 @@ https://docs.autopilotmonitor.com (separate repository).
|
||||
* [Zero User-Targeted Apps — the starved user-phase completion gate](agent/zero-user-apps-completion-gate.md) - Why a device with no user-targeted Intune apps could never satisfy the IME user-session gate — IME skips its "Completed user session" line entirely in the zero-app branch — turning the 30-minute completion backstop into `esp_exit_without_completion_evidence` on a third of one tenant's enrollments, with the user at the desktop and RealmJoin still installing. The two fixes: read the line IME does write (IME-USER-SESSION-ZERO-APPS), and never fail a session while a completion gate with its own armed deadline is deliberately holding it.
|
||||
* [Secure Boot CA-2023 Detection](agent/secureboot-ca2023-detection.md) - Why ANALYZE-SEC-001 v3 reads the UEFI db/KEK variables directly instead of trusting the SecureBoot\Servicing registry, and how the one-sided uefiCa2023FirmwareConfirmed marker plus a not_exists precondition give absence-tolerant suppression across old and new agents.
|
||||
* [Continue-Anyway Observation Mode](agent/continue-anyway-observation.md) - EnableEspContinueAnywayObservation (ConfigVersion 37, operator-set): Device-phase ESP terminal failures on Continue-Anyway profiles get a bounded 60-min observation instead of an immediate hard fail; the DAD-validated real-user desktop + Hello gate detect the true end, the session completes Succeeded + EspSoftFailure (amber "with issues" badge), expiry un-defangs to the original esp_terminal_failure.
|
||||
* [CMTrace Time Resolution](agent/cmtrace-time-resolution.md) - Converting bias-less CMTrace local times to UTC without trusting any process's timezone belief: per-line self-anchoring on the 15-minute offset grid for provably fresh lines, uniform marked reader-zone fallback for backlog, and the provenance vocabulary (bias / line-anchored / reader-zone-fallback) that keeps every event re-derivable.
|
||||
* [CMTrace Time Resolution](agent/cmtrace-time-resolution.md) - Converting bias-less CMTrace local times to UTC without trusting any process's timezone belief: per-line self-anchoring on the 15-minute offset grid for provably fresh lines, uniform marked reader-zone fallback for backlog, and the provenance vocabulary (bias / line-anchored / reader-zone-fallback) that keeps every event re-derivable; plus the linear-cost CMTrace line parser (first open / last parseable trailer, timeout-backed) and the agent multiline caps.
|
||||
* [IME Pattern Health — the pattern-drift loop](backend/ime-pattern-health.md) - How a Microsoft change to IME log wording becomes visible before it breaks timelines: tracker health counters in `agent_metrics_snapshot`, the one-shot `ime_tracker_degraded` Warning, the session-end `ime_pattern_hits` histogram folded into the global per-IME-version `ImePatternStats` table, the data-driven drift alarm (`ImePatternDriftSuspected`: an expected pattern — ≥80 % on the fleet baseline — matched in none of ≥25 sessions on a newer version), the Global Admin → Metrics → IME Pattern Health matrix and the `get_ime_pattern_health` MCP tool.
|
||||
* [IME Log Tracker — Anchored Patterns and the Bounded Read Loop](agent/ime-log-tracker-matching-budget.md) - The IME Logs folder is user-writable, so the SYSTEM tracker must stay linear on hostile input: every shipped pattern starts with `^` (a non-match is O(prefix)), no chained unbounded scans, a byte-bounded line reader (32 MB — real `Get policies` lines reach 5.6 MB) with exact bookmarks, a 2 s per-line matching budget, held-back unterminated tails, and one Warning per pass instead of silent Debug timeouts.
|
||||
* [Registry App-State Second Pillar](agent/registry-app-state-pillar.md) - IME's authoritative per-app state read straight from the registry (Win32Apps EnforcementStateMessage, EspTrackingWin32Apps, StatusServiceReports) via a snapshot-and-diff watcher; registry/log reconciliation doubles as the built-in IME log-pattern drift alarm.
|
||||
* [WDP Scenario Gates & Completion Backstop](agent/wdp-scenario-gates.md) - How the agent gates Autopilot/ESP-only machinery off on Windows Device Preparation sessions (deterministic ExecutionContext marker, fail-toward-Classic), which noise it suppresses (ZTD probe, esp_config unknown, ModernDeployment 100/417/418/1005, starved sweep), and the WDP completion model: High-confidence seed, desktop-based arm D, and the 30-min device_prep_completion backstop.
|
||||
* [IME Process Watcher — Identity Before Attach, Re-Arm After Exit](agent/ime-process-watcher.md) - `ime_process_exited` comes only from a process that runs in session 0 with its image under the IME install root (QueryFullProcessImageName, cross-bitness) — a process name is not an identity (CWE-807: a standard user can start a renamed binary during AccountSetup); the oldest trusted candidate wins, untrusted matches are logged once per PID and never attached, and a reported exit re-arms discovery so an IME restart and a later crash are both seen.
|
||||
* [Local Admin Analyzer](agent/local-admin-analyzer.md) - What local_admin_analysis asserts at enrollment start/end: every local account inventoried, disabled ones included (a /active:no backdoor is dormant at both scans), Administrators membership read from the SAM by well-known SID (NetLocalGroupGetMembers, WMI-independent), unexpected admin members scored +40, built-ins matched by well-known RID (localized Guest/Administrator never flagged), shutdown-time dynamic allowance never covers a local SAM account (CWE-807), junctions and $GUID folders are not profiles, non-local members (Entra role SIDs) listed but never flagged, built-in Administrator enabled-state reported but not scored.
|
||||
* [Power-State Watcher](agent/power-state-watcher.md) - Live AC↔battery transitions and 50/30/15% downward battery-threshold crossings during enrollment via a WMI Win32_PowerManagementEvent push subscription (zero polling, never armed on battery-less devices): the power_state_change event (transition field, per-level lifetime latch, 5s debounce, emission cap) and the analyze rules ANALYZE-DEV-009 (battery <15%, high) / ANALYZE-DEV-010 (AC→battery, warning) with on_event interim triggers.
|
||||
* [System Timeline Watcher](agent/system-timeline-watcher.md) - Ground truth for two timeline blind spots from one System-channel EventLogWatcher with pre-agent backfill: system_clock_changed (Kernel-General 1 — oldTime/newTime/signed timeDeltaMs/reason/setting process; 2 s micro-slew floor, ≥5 min Warning+immediate) and system_sleep_episode (Power-Troubleshooter 1 sleep/hibernate + Kernel-Power 507 Modern Standby; authoritative enteredAt/exitedAt payload instants, 60 s Modern-Standby floor). Consumed by the TimeJumpBadge clock-set cause, the SessionInfoCard standby note, time-attribution SleepSpans (AttributionVersion 3) and ANALYZE-DEV-012.
|
||||
* [DO GroupId Auto-Set](agent/do-group-id-auto-set.md) - EnableDoGroupIdAutoSet (ConfigVersion 40): the agent seeds the Delivery Optimization DOGroupId policy with a deterministic network-fingerprint GUID (default gateway IPv4 + SendARP MAC, RealmJoin-compatible byte layout) so same-network devices form one DO peering group during ESP; ownership-tracked via the StartupEventGate fingerprint, foreign DOGroupId/DOGroupIdSource policies always win, no random-GUID fallback.
|
||||
@@ -83,7 +87,7 @@ https://docs.autopilotmonitor.com (separate repository).
|
||||
* [Vulnerability Correlation Sources](backend/vulnerability-correlation-sources.md) - How the software inventory becomes CVE findings from four cached primaries (NVD per-CPE cache with budget and TTLs, CISA KEV, MSRC, FIRST EPSS stored on the cached CVE and re-scored nightly), the NVD throttle contract (a refused answer is never cached as "0 CVEs"; bounded process-wide cooldown honouring Retry-After), the persisted CVSS vector, and the act/attend/track priority that keeps severity and likelihood as two visible signals instead of one composite score.
|
||||
* [Client-Certificate Tenant Binding](backend/client-cert-tenant-binding.md) - Why a chain-valid Intune MDM certificate only proves "issued to some tenant", which extension actually carries the Entra tenant id (`…5.14`) versus the two that look like it (`…5.6` Intune Account id, `…5.4` device id, different encodings), why the Entra join certificate is not part of the auth path, the shadow-then-enforce rollout with its volume-controlled telemetry, and why `X-ARR-ClientCert` is the only certificate transport (TLS handshake = proof of possession; bootstrap routes token-only, fail-closed).
|
||||
* [Admin Identity Binding](backend/admin-identity-binding.md) - Why a GlobalAdmins / DelegatedAdmins row keyed on a UPN string must never confer access by itself in an API that accepts tokens from every Entra tenant (domain re-registration, UPN recycling), the AdminIdentityBindings table pinning each cross-tenant-role UPN to its immutable home tid + oid, the fail-closed resolution order in both role services (row → binding → entitlement), the first-sign-in object-id pin with its race semantics, the 409 on grant-time conflicts, and the explicit GET/PUT/DELETE rebind surface.
|
||||
* [IME Installer Archive](backend/ime-msi-archive.md) - How every fleet-observed IME build is archived as its MSI: the ime_agent_version sighting that queues the job (first sighting, or a later sighting with a version-authoritative CSP URL after a 24 h backoff), the primary/secondary/hotfix candidate walk, the managed MSI ProductVersion check that keeps another ring's build out of the version folder (Failed:VersionMismatch), provenance with the full candidate walk, and the skill-side overwrite path for corrections.
|
||||
* [IME Installer Archive](backend/ime-msi-archive.md) - How every fleet-observed IME build is archived as its MSI: the ime_agent_version sighting that queues the job (first sighting, or a later sighting with a version-authoritative CSP URL after a 24 h backoff), the primary/secondary/hotfix candidate walk, the managed MSI ProductVersion check that keeps another ring's build out of the version folder (Failed:VersionMismatch), provenance with the full candidate walk, the skill-side overwrite path for corrections, and the trust boundary of a device-reported version (plausibility guard, one sighting per session, second-tenant corroboration, fleet-confirmed tenant view).
|
||||
|
||||
# Conventions for this bundle
|
||||
|
||||
|
||||
+58
@@ -1,5 +1,63 @@
|
||||
# Log
|
||||
|
||||
## 2026-08-30 (11)
|
||||
|
||||
* **New**: `agent/ime-process-watcher.md` — `ImeProcessWatcher` no longer attaches to the first `IntuneManagementExtension` name match: `ImeProcessIdentity` requires session 0 + image under the IME install root (`QueryFullProcessImageName`, cross-bitness), prefers the oldest trusted candidate, logs untrusted matches once per PID, and re-arms discovery after every reported exit (previously one-shot — a fake in a user session could forge `ime_process_exited` and mute the real signal for the session; even an IME service restart was never watched again). Registered in `index.md`.
|
||||
|
||||
## 2026-08-30 (10)
|
||||
|
||||
* **Update**: `agent/ime-log-tracker-matching-budget.md` — the per-line budget sums only `Regex.Match` time; the previous wall-clock window also counted match handlers and scheduler stalls and broke twice on genuine lines of a stalled Hyper-V guest (session 946ccbd6). `backend/ime-pattern-health.md` — `ime_pattern_hits` is Debug severity (operator signal, hidden from the default timeline; the IME Tracker card and the ingest fold read it regardless). The session page's "IME Tracker" card moved to the right column.
|
||||
|
||||
## 2026-08-30 (9)
|
||||
|
||||
* **New**: `backend/ime-pattern-health.md` — the pattern-drift loop: `ImeTrackerHealth` counters + per-pattern histogram in the agent (restart-safe via `ImeTrackerState`), `ime_*` keys in `agent_metrics_snapshot`, one-shot `ime_tracker_degraded` Warning, session-end `ime_pattern_hits` (every enabled pattern, zeros included, `imeVersion`), backend `ImePatternStats` (PK = IME version, batch upsert, permanent), `ImePatternDriftEvaluator` (baseline = biggest version ≥100 sessions, expected = ≥0.8 hit rate, drift = 0 hits in ≥25 sessions, once per cell) → `ImePatternDriftSuspected` ops event, `GET metrics/ime-pattern-health` (GlobalReadOrAdmin), web section `admin/metrics/ime-pattern-health` + session-page "IME Tracker" rows, MCP `get_ime_pattern_health`. Registered in `index.md`.
|
||||
|
||||
## 2026-08-30 (8)
|
||||
|
||||
* **Update**: `agent/ime-log-tracker-matching-budget.md` — pattern-pack hygiene against the IME decompiles (1.97.107.0 / 1.104.102.0 / 1.105.103.0, spot checks 1.50 / 1.83): no wording drift in any shipped pattern; the 11 disabled legacy patterns (IME-DETECTED-OLD, IME-SET-CURRENT-1/2/3, IME-SKIP-APPLICABILITY-NOTMET, IME-SKIP-DEPENDENCY-DETECT, IME-SKIP-DETECTED, IME-SKIP-UNINSTALL-NOTDETECTED-2, IME-UPDATE-NAME-1/2/3) are deleted — their literals last existed in IME 1.50, the fleet starts at 1.97; IME-DO-TIMEOUT-1/2 match the exact source literal (`DO downloading is not finished within timeout, jobId = …, fileId = …`) instead of two lookaheads; IME-DO-TEL is documented as dead from 1.104 on and kept only for the 1.101/1.103 fleet share.
|
||||
|
||||
## 2026-08-30 (7)
|
||||
|
||||
* **New**: `agent/ime-log-tracker-matching-budget.md` — the shipped IME pattern pack is anchored (`^` on all 90 patterns, `IME-APP-VERSION` without the chained `[^}]*?` scan) and the tracker's read loop is bounded: `BoundedLineReader` (32 MB per line, exact byte bookmarks), 2 s per-line matching budget, held-back unterminated EOF tails with a 1 s settle, one Warning per pass (`oversizedLines` / `regexTimeouts` / `lineBudgetBreaks`); the unanchored `PS-SCRIPT-CONTEXT` false positives on `[Win32App] SideCarScript…` lines are gone. `--run-ime-matching` assembles multiline entries like the tracker. Registered in `index.md`.
|
||||
|
||||
## 2026-08-30 (6)
|
||||
|
||||
* **Update**: `rules/rule-id-namespace.md` — guard 1 rewritten: gather RuleStats (tenant + `global_*` rows) are now resolved against the tenant's active gather catalog (`EventIngestProcessor.ResolveFiredGatherRules`), metadata catalog-sourced, unknown IDs dropped — same design as the analyze path; the `IsReservedBuiltInId` pattern gate on device-supplied IDs is gone.
|
||||
|
||||
## 2026-08-30 (5)
|
||||
|
||||
* **Update**: `backend/admin-identity-binding.md` — new "The MCP whitelist is a third UPN-keyed grant" section: `McpUsers` rows (grant, `UsagePlan` override and Disabled kill-switch) are honoured only for the identity the UPN is bound to (`McpUserService` row → `IsBoundAsync`, `GetBoundMcpUserAsync` for the quota plan); `POST global/mcp-users` binds first with the shared resolver / 422 / 409 contract; unbound legacy rows are inert.
|
||||
|
||||
## 2026-08-30 (4)
|
||||
|
||||
* **Update**: `backend/ime-msi-archive.md` — new "Trust boundary of a sighting" section: the device-supplied `ime_agent_version` string is gated by `ImeMsiArchiver.IsPlausibleVersion` (dotted digits within MSI ProductVersion bounds) before it can become a GLOBAL `ImeVersionHistory` RowKey (`ImeVersionSighting.Rejected` → no row/ops event/archive job); a session counts once (`UpdateSessionImeAgentVersionAsync` returns whether the version changed); a second tenant's sighting stamps `CorroboratedAt`; the MemberRead projection lists only fleet-confirmed versions (Archived, corroborated, or first seen before 2026-08-31), Global scope sees every row.
|
||||
|
||||
## 2026-08-30 (3)
|
||||
|
||||
* **Update**: `agent/local-admin-analyzer.md` — built-ins matched by well-known RID (500/501/503/504) so localized Guest/Administrator are not flagged now that disabled accounts are inventoried; `kioskUser0` built-in; shutdown-time dynamic allowance restricted to non-SAM identities (a logged-in local account is never exempted — CWE-807) with new `logged_in_local_accounts` field; junctions and `$<32 hex>` folders skipped by the profile check.
|
||||
|
||||
* **New**: `agent/local-admin-analyzer.md` — `LocalAdminAnalyzer` no longer skips disabled accounts (a `/active:no` backdoor re-enabled after enrollment was invisible at both scans) and reads Administrators-group membership from the SAM via `NetLocalGroupGetMembers` on `S-1-5-32-544` (`LocalGroupNativeMethods`, WMI-independent). New event fields `unexpected_admin_members` (+40), `account_details`, `administrators_group.{enumerated,error_code,members}`, `builtin_administrator_enabled` (reported, not scored). Non-local members (Entra role SIDs, domain groups) are listed, never flagged. `ANALYZE-ID-002` 1.2.0 explanation names the new fields. Registered in `index.md`.
|
||||
|
||||
## 2026-08-30 (2)
|
||||
|
||||
* **Update**: `agent/cmtrace-time-resolution.md` — new "Line parsing (bounded cost)" section: `CmTraceLogParser.TryParseLine` is no longer one greedy-`.*` regex (quadratic on hostile lines, no timeout) but a linear first-open / last-parseable-trailer search with a `\G`-anchored, timeout-backed trailer regex that returns exactly what the old regex did; the agent's multiline buffer gains a 1 MB char cap next to the 100-line cap, both logged at Warning, and the tail of a dropped entry is skipped instead of raw-matched.
|
||||
|
||||
## 2026-08-30 (1)
|
||||
|
||||
* **Update**: `agent/diagnostics-package.md` — reads are validated on the handle, not the path: `PinnedSourceFolder` pins each section folder (its final path must equal the validated path; held open so the chain cannot be renamed) and opens every file with `FILE_FLAG_OPEN_REPARSE_POINT`, requiring the handle's final path to be `<canonical folder>\<relative path>`. A subdirectory swapped for a junction between enumeration and open is skipped (`SKIPPED (resolved outside validated folder)`) instead of copied into the package.
|
||||
|
||||
## 2026-08-29 (12)
|
||||
|
||||
* **Update**: `backend/tenant-activation-welcome-mail.md` — `PUT preview/notification-email` moved to `AuthenticatedUserWithRole`; roleless callers may write the address only while the tenant has no enabled member (signup window), afterwards a member role is required.
|
||||
|
||||
## 2026-08-29 (11)
|
||||
|
||||
* **Update**: `rules/gather-rule-phase-scoping.md` — `on_event` feedback-loop guard: `TimelineEventStream` publishes the event source and `GatherRuleExecutorHost` never dispatches `on_event` for gather-emitted events (a rule triggering on its own output type looped for the whole session); per-rule on_event execution cap (100/session); backend rejects `on_event` rules whose trigger equals their output type or `gather_result`.
|
||||
|
||||
## 2026-08-29 (10)
|
||||
|
||||
* **Update**: `backend/tenant-activation-welcome-mail.md` — welcome/farewell mails HTML-encode the domain at substitution; `DomainName` is frozen on the full-model config PUT (was persisted verbatim) and the first-login seed only accepts a strict DNS host name.
|
||||
|
||||
## 2026-08-29 (9)
|
||||
|
||||
* **Update**: `agent/decision-engine.md`, `agent/overview.md` — `SignalIngress.Post` is worker-thread re-entrancy-safe: signals the worker posts to itself from inside `ApplyStep` (EffectRunner `ClassifierVerdictIssued`) go to a worker-local follow-up queue drained ahead of the bounded channel instead of blocking on it, so a full channel can no longer deadlock the single consumer.
|
||||
|
||||
@@ -8,7 +8,7 @@ tags:
|
||||
- backend
|
||||
- gather-rules
|
||||
- config
|
||||
timestamp: 2026-08-04T00:00:00+02:00
|
||||
timestamp: 2026-08-29T00:00:00+02:00
|
||||
---
|
||||
|
||||
# Problem
|
||||
@@ -89,6 +89,18 @@ Consequences of the emitted-event feed:
|
||||
the raw signal stream — the documented "On Event with `enrollment_complete`" end-of-enrollment
|
||||
pattern only works because of this. This restores V1 semantics (V1 fired triggers from every
|
||||
emitted `EnrollmentEvent`).
|
||||
* **Feedback-loop guard** (2026-08-29): gather output travels the same post → ingress →
|
||||
reducer → emitter → stream path, so the stream would hand the executor's own events straight
|
||||
back to `OnEvent`. A rule with `triggerEventType == outputEventType` (or two rules chained
|
||||
A→B→A) then executed and emitted without bound for the whole session — `emitMode:
|
||||
on_change` does not help when the collected result varies. `TimelineEventStream` therefore
|
||||
publishes the event **source**, and `GatherRuleExecutorHost` never dispatches `on_event` for
|
||||
`Source == GatherRuleExecutor.SourceName` (phase declarations on such events are still
|
||||
honoured). Second layer: `GatherRuleExecutor.MaxOnEventExecutionsPerRule` (100 per rule and
|
||||
session) bounds any cycle that reaches the executor through a non-gather event; the boundary
|
||||
is logged once (agent log Warning + debug-trace `trigger` line). Backend validation rejects
|
||||
`on_event` rules whose trigger equals their own output type or `gather_result` — such a rule
|
||||
could never fire.
|
||||
* Other hosts (`DeviceInfoHost`, `UserEspKeepAwakeHost`, `ProvisioningPackageHost`) still key
|
||||
off the raw signal stream deliberately — their collections are about OS state at the raw
|
||||
boundary (ESP exit, keep-awake), not about timeline agreement.
|
||||
@@ -103,7 +115,8 @@ Consequences of the emitted-event feed:
|
||||
`_startupRulesExecuted` dedup; it is not part of the `WaitForStartupRules` latch).
|
||||
* `phase_change` — evaluated against the **new** phase; the scope gate runs before the
|
||||
per-(rule, phase) dedup so an out-of-scope pass does not consume the execution slot.
|
||||
* `on_event` — gated by the current phase.
|
||||
* `on_event` — gated by the current phase; never fires on gather output; capped at
|
||||
`MaxOnEventExecutionsPerRule` per rule and session.
|
||||
* **Before the first phase signal** of a session (`_currentPhase == Unknown`) scoped rules are
|
||||
inactive. Sessions that never produce phase signals never run them (documented behavior).
|
||||
* **From-phase latch**: activates when `phase != Unknown && phase != Failed && (int)phase >=
|
||||
@@ -164,4 +177,4 @@ Consequences of the emitted-event feed:
|
||||
* `src/Backend/AutopilotMonitor.Functions/Functions/Rules/GatherRulesFunction.cs` — `ValidateScopeAndEmitMode`
|
||||
* `src/Backend/AutopilotMonitor.Functions/Services/GatherRuleService.cs` — partial-PUT merge, `ContentEquivalent`
|
||||
* `src/Shared/AutopilotMonitor.Shared/Models/Rules/GatherRule.cs` — field contracts
|
||||
* Tests: `GatherRuleExecutorPhaseScopeTests`, `GatherRuleExecutorEmitModeTests`, `GatherRuleExecutorHostTimelineFeedTests` (agent); `GatherRuleUpdatePartialMergeTests`, `GatherRuleScopeFieldsTests` (backend)
|
||||
* Tests: `GatherRuleExecutorPhaseScopeTests`, `GatherRuleExecutorEmitModeTests`, `GatherRuleExecutorHostTimelineFeedTests`, `GatherRuleExecutorOnEventCapTests` (agent); `GatherRuleUpdatePartialMergeTests`, `GatherRuleScopeFieldsTests` (backend)
|
||||
|
||||
@@ -31,7 +31,7 @@ Namespace split (`RuleIdPolicy.cs`, shared model):
|
||||
|
||||
# Guards (all enforced, 2026-08-21)
|
||||
|
||||
1. **Global RuleStats aggregate is catalog-only.** The `global_{ruleId}` row would otherwise sum same-ID custom rules of unrelated tenants (title/severity last-writer-wins). Analyze gates on `rule.IsBuiltIn || rule.IsCommunity` (`AnalyzeOnEnrollmentEndHandler`), gather gates on `RuleIdPolicy.IsReservedBuiltInId` (`EventIngestProcessor.RuleStats`) because agent events carry no flag — valid because custom rules can never occupy the reserved pattern.
|
||||
1. **Global RuleStats aggregate is catalog-only.** The `global_{ruleId}` row would otherwise sum same-ID custom rules of unrelated tenants (title/severity last-writer-wins). Both paths gate on the server-side catalog rule object: analyze on `rule.IsBuiltIn || rule.IsCommunity` of the evaluated rule (`AnalyzeOnEnrollmentEndHandler`), gather on the same flags of the rule resolved from the tenant's active gather catalog (`EventIngestProcessor.ResolveFiredGatherRules`). The agent event contributes only the `ruleId`; title/category/severity are taken from the catalog, never from event `Data`, and a claimed ID that is not an active rule for the tenant is dropped — so a device cannot mint `global_*` rows for retired/unshipped IDs, inflate a built-in rule's counters under a foreign ID, or respell its title. (Earlier the gather path selected the global row by `RuleIdPolicy.IsReservedBuiltInId` on the device-supplied ID and wrote the device-supplied title — pattern membership is not catalog membership.)
|
||||
2. **Catalog build fails on namespace violations.** `rules/scripts/combine.js` rejects duplicate IDs (case-insensitive) and any gather/analyze built-in outside the reserved pattern; `BuiltInRuleCatalogPolicyTests` pins the same invariants on the embedded resource actually deployed.
|
||||
3. **GitHub reseed rejects non-reserved IDs** before writing to the `global` partition (`ReseedFromGitHubFunction`) — a seeded `*-CUSTOM-*` rule would silently shadow every tenant's same-ID custom rule at merge time.
|
||||
4. **Cross-tenant RuleState GC is namespace-gated.** `DeleteRuleStatesForRuleIdAcrossTenantsAsync` deletes by bare RowKey across all partitions; it now refuses non-reserved IDs with `failed=-1` (callers then skip the global catalog delete, same contract as enumeration failure).
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"description": "Detects when the LocalAdminAnalyzer flags unexpected local user accounts or profile directories on the device. This is a known Autopilot bypass technique where admin accounts are created before or during enrollment to gain persistent local admin access.",
|
||||
"severity": "high",
|
||||
"category": "identity",
|
||||
"version": "1.1.0",
|
||||
"version": "1.3.0",
|
||||
"author": "Autopilot Monitor",
|
||||
"enabled": true,
|
||||
"evaluateOn": [
|
||||
@@ -56,7 +56,7 @@
|
||||
}
|
||||
],
|
||||
"confidenceThreshold": 50,
|
||||
"explanation": "The agent detected unexpected local user accounts or profile directories on this device that are not in the built-in or tenant-configured allowed list.\n\nThis is a security-relevant finding. Creating local admin accounts before or during Autopilot enrollment is a known bypass technique (e.g. via BypassNRO or Shift+F10 during OOBE) that grants persistent local administrator access.\n\nClick the event link below to jump to the local_admin_analysis event and check: unexpected_accounts (local user accounts not on the allowed list), unexpected_profiles (C:\\Users profile directories not on the allowed list), bypass_nro (whether the BypassNRO registry flag was set), and confidence_score (the agent's confidence in the finding, 0-100).\n\nIf these accounts are expected for your environment (e.g. a service account or helpdesk account), add them to the allowed list in Tenant Settings.",
|
||||
"explanation": "The agent detected unexpected local user accounts or profile directories on this device that are not in the built-in or tenant-configured allowed list.\n\nThis is a security-relevant finding. Creating local admin accounts before or during Autopilot enrollment is a known bypass technique (e.g. via BypassNRO or Shift+F10 during OOBE) that grants persistent local administrator access.\n\nClick the event link below to jump to the local_admin_analysis event and check: unexpected_accounts (local user accounts not on the allowed list — disabled accounts included, a dormant account can be re-enabled later), unexpected_admin_members (unexpected accounts that are members of the local Administrators group — the actual backdoor), account_details (per account: disabled state and Administrators membership), unexpected_profiles (C:\\Users profile directories not on the allowed list), logged_in_local_accounts (local accounts that were signed in when the shutdown scan ran — a signed-in local account is never exempted, only Entra/domain users are), bypass_nro (whether the BypassNRO registry flag was set), and confidence_score (the agent's confidence in the finding, 0-100).\n\nIf these accounts are expected for your environment (e.g. a service account or helpdesk account), add them to the allowed list in Tenant Settings.",
|
||||
"remediation": [
|
||||
{
|
||||
"title": "Review the unexpected accounts",
|
||||
@@ -79,7 +79,7 @@
|
||||
"steps": [
|
||||
"Check if the BypassNRO registry flag was set (indicates OOBE was bypassed)",
|
||||
"Review the enrollment timeline for Shift+F10 or other OOBE escape indicators",
|
||||
"Check whether the unexpected account has admin group membership",
|
||||
"Check 'unexpected_admin_members' and 'account_details' — an unexpected account with Administrators membership is a backdoor even while it is disabled (it can be re-enabled by a scheduled task after enrollment)",
|
||||
"Consider re-imaging the device if unauthorized admin access is confirmed"
|
||||
]
|
||||
}
|
||||
|
||||
+3
-3
@@ -2766,7 +2766,7 @@
|
||||
"description": "Detects when the LocalAdminAnalyzer flags unexpected local user accounts or profile directories on the device. This is a known Autopilot bypass technique where admin accounts are created before or during enrollment to gain persistent local admin access.",
|
||||
"severity": "high",
|
||||
"category": "identity",
|
||||
"version": "1.1.0",
|
||||
"version": "1.3.0",
|
||||
"author": "Autopilot Monitor",
|
||||
"enabled": true,
|
||||
"evaluateOn": [
|
||||
@@ -2817,7 +2817,7 @@
|
||||
}
|
||||
],
|
||||
"confidenceThreshold": 50,
|
||||
"explanation": "The agent detected unexpected local user accounts or profile directories on this device that are not in the built-in or tenant-configured allowed list.\n\nThis is a security-relevant finding. Creating local admin accounts before or during Autopilot enrollment is a known bypass technique (e.g. via BypassNRO or Shift+F10 during OOBE) that grants persistent local administrator access.\n\nClick the event link below to jump to the local_admin_analysis event and check: unexpected_accounts (local user accounts not on the allowed list), unexpected_profiles (C:\\Users profile directories not on the allowed list), bypass_nro (whether the BypassNRO registry flag was set), and confidence_score (the agent's confidence in the finding, 0-100).\n\nIf these accounts are expected for your environment (e.g. a service account or helpdesk account), add them to the allowed list in Tenant Settings.",
|
||||
"explanation": "The agent detected unexpected local user accounts or profile directories on this device that are not in the built-in or tenant-configured allowed list.\n\nThis is a security-relevant finding. Creating local admin accounts before or during Autopilot enrollment is a known bypass technique (e.g. via BypassNRO or Shift+F10 during OOBE) that grants persistent local administrator access.\n\nClick the event link below to jump to the local_admin_analysis event and check: unexpected_accounts (local user accounts not on the allowed list — disabled accounts included, a dormant account can be re-enabled later), unexpected_admin_members (unexpected accounts that are members of the local Administrators group — the actual backdoor), account_details (per account: disabled state and Administrators membership), unexpected_profiles (C:\\Users profile directories not on the allowed list), logged_in_local_accounts (local accounts that were signed in when the shutdown scan ran — a signed-in local account is never exempted, only Entra/domain users are), bypass_nro (whether the BypassNRO registry flag was set), and confidence_score (the agent's confidence in the finding, 0-100).\n\nIf these accounts are expected for your environment (e.g. a service account or helpdesk account), add them to the allowed list in Tenant Settings.",
|
||||
"remediation": [
|
||||
{
|
||||
"title": "Review the unexpected accounts",
|
||||
@@ -2840,7 +2840,7 @@
|
||||
"steps": [
|
||||
"Check if the BypassNRO registry flag was set (indicates OOBE was bypassed)",
|
||||
"Review the enrollment timeline for Shift+F10 or other OOBE escape indicators",
|
||||
"Check whether the unexpected account has admin group membership",
|
||||
"Check 'unexpected_admin_members' and 'account_details' — an unexpected account with Administrators membership is a backdoor even while it is disabled (it can be re-enabled by a scheduled task after enrollment)",
|
||||
"Consider re-imaging the device if unauthorized admin access is confirmed"
|
||||
]
|
||||
}
|
||||
|
||||
+81
-169
@@ -4,7 +4,7 @@
|
||||
{
|
||||
"patternId": "HS-COMPLIANCE",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] the (?<part>pre|post)-rem(?:e)?diation detection script compliance result for (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) is (?<compliance>True|False)",
|
||||
"pattern": "^\\[HS\\] the (?<part>pre|post)-rem(?:e)?diation detection script compliance result for (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) is (?<compliance>True|False)",
|
||||
"action": "healthScriptDetectionResult",
|
||||
"parameters": {
|
||||
"scriptType": "remediation"
|
||||
@@ -15,7 +15,7 @@
|
||||
{
|
||||
"patternId": "HS-COMPLIANCE-POST",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] the (?<part>post) detection script result for (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) is (?<compliance>True|False)",
|
||||
"pattern": "^\\[HS\\] the (?<part>post) detection script result for (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) is (?<compliance>True|False)",
|
||||
"action": "healthScriptDetectionResult",
|
||||
"parameters": {
|
||||
"scriptType": "remediation"
|
||||
@@ -26,7 +26,7 @@
|
||||
{
|
||||
"patternId": "HS-EXITCODE",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] exit code of the script is (?<exitCode>-?\\d+)",
|
||||
"pattern": "^\\[HS\\] exit code of the script is (?<exitCode>-?\\d+)",
|
||||
"action": "scriptExitCode",
|
||||
"parameters": {
|
||||
"scriptType": "remediation"
|
||||
@@ -37,7 +37,7 @@
|
||||
{
|
||||
"patternId": "HS-NEW-RESULT",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] new result = (?<json>\\{[\\s\\S]*\\})\\s*$",
|
||||
"pattern": "^\\[HS\\] new result = (?<json>\\{[\\s\\S]*\\})\\s*$",
|
||||
"action": "healthScriptResult",
|
||||
"parameters": {
|
||||
"scriptType": "remediation"
|
||||
@@ -48,7 +48,7 @@
|
||||
{
|
||||
"patternId": "HS-RUN-CONTEXT",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] The policy needs be run as (?<context>System|User)",
|
||||
"pattern": "^\\[HS\\] The policy needs be run as (?<context>System|User)",
|
||||
"action": "scriptContext",
|
||||
"parameters": {
|
||||
"scriptType": "remediation"
|
||||
@@ -59,7 +59,7 @@
|
||||
{
|
||||
"patternId": "HS-SCRIPT-START",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] ProcessScript PolicyId: (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) PolicyType: (?<policyType>\\d+)",
|
||||
"pattern": "^\\[HS\\] ProcessScript PolicyId: (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) PolicyType: (?<policyType>\\d+)",
|
||||
"action": "scriptStarted",
|
||||
"parameters": {
|
||||
"scriptType": "remediation"
|
||||
@@ -70,7 +70,7 @@
|
||||
{
|
||||
"patternId": "HS-STDERR",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] err output = (?<output>.*)",
|
||||
"pattern": "^\\[HS\\] err output = (?<output>.*)",
|
||||
"action": "scriptOutput",
|
||||
"parameters": {
|
||||
"scriptType": "remediation",
|
||||
@@ -82,7 +82,7 @@
|
||||
{
|
||||
"patternId": "HS-STDOUT",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] std output = (?<output>.*)",
|
||||
"pattern": "^\\[HS\\] std output = (?<output>.*)",
|
||||
"action": "scriptOutput",
|
||||
"parameters": {
|
||||
"scriptType": "remediation",
|
||||
@@ -94,7 +94,7 @@
|
||||
{
|
||||
"patternId": "IME-AGENT-VERSION",
|
||||
"category": "always",
|
||||
"pattern": "Agent version is: (?<agentVersion>[\\d.]+)",
|
||||
"pattern": "^Agent version is: (?<agentVersion>[\\d.]+)",
|
||||
"action": "imeAgentVersion",
|
||||
"description": "Captures the Intune Management Extension agent version number.",
|
||||
"enabled": true
|
||||
@@ -102,7 +102,7 @@
|
||||
{
|
||||
"patternId": "IME-ALREADY-DETECTED-1",
|
||||
"category": "otherPhases",
|
||||
"pattern": "\\[Win32App\\] Completed detectionManager SideCar\\w+?DetectionManager, applicationDetectedByCurrentRule: True",
|
||||
"pattern": "^\\[Win32App\\] Completed detectionManager SideCar\\w+?DetectionManager, applicationDetectedByCurrentRule: True",
|
||||
"action": "ignoreCompletedApp",
|
||||
"description": "Detects when an app from a previous ESP phase is detected as already installed, so it should be ignored in the current phase.",
|
||||
"enabled": true
|
||||
@@ -110,7 +110,7 @@
|
||||
{
|
||||
"patternId": "IME-ALREADY-DETECTED-2",
|
||||
"category": "otherPhases",
|
||||
"pattern": "\\[Win32App\\] Setting enforcementState as\\W+Success\\b",
|
||||
"pattern": "^\\[Win32App\\] Setting enforcementState as\\W+Success\\b",
|
||||
"action": "ignoreCompletedApp",
|
||||
"description": "Detects enforcement success in a non-current phase, indicating an app completed in a previous phase should be ignored.",
|
||||
"enabled": true
|
||||
@@ -118,7 +118,7 @@
|
||||
{
|
||||
"patternId": "IME-APP-TYPE-MSI",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\].*?\\bMSI App (?<id>{GUID})",
|
||||
"pattern": "^\\[Win32App\\].*?\\bMSI App (?<id>{GUID})",
|
||||
"action": "captureAppTypeMsi",
|
||||
"description": "Detects an MSI-based Win32 app via the 'MSI App <guid>' phrase in the ESP retry configuration line. Marks AppType as 'MSI' for that app id.",
|
||||
"enabled": true
|
||||
@@ -126,7 +126,7 @@
|
||||
{
|
||||
"patternId": "IME-APP-TYPE-WINGET",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\]\\[WinGetApp\\].*?app with id: (?<id>{GUID})",
|
||||
"pattern": "^\\[Win32App\\]\\[WinGetApp\\].*?app with id: (?<id>{GUID})",
|
||||
"action": "captureAppTypeWinGet",
|
||||
"description": "Detects a WinGet-backed Store app by any [Win32App][WinGetApp] handler log line (Detection/Applicability/Operation). Marks AppType as 'WinGet' for that app id.",
|
||||
"enabled": true
|
||||
@@ -134,7 +134,7 @@
|
||||
{
|
||||
"patternId": "IME-APP-VERSION",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Detection state for app with id: (?<id>{GUID}).*?\"DetectedIdentityVersion\":\\{[^}]*?\"NewValue\":\"(?<appVersion>[^\"]+)\"",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Detection state for app with id: (?<id>{GUID}).*?\"DetectedIdentityVersion\":\\{(?:\"OldValue\":[^,}]{0,64},)?\"NewValue\":\"(?<appVersion>[^\"]+)\"",
|
||||
"action": "captureAppVersion",
|
||||
"description": "Captures the product version IME detected after an app install (e.g. \"11.2.1787.0\"). Source: [ReportingManager] Detection state update with DetectedIdentityVersion.NewValue. Used by App Dashboard to show version-level failure breakdowns.",
|
||||
"enabled": true
|
||||
@@ -142,7 +142,7 @@
|
||||
{
|
||||
"patternId": "IME-ATTEMPT-NUMBER",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] ===Step=== Execute retry (?<attempt>\\d+)$",
|
||||
"pattern": "^\\[Win32App\\] ===Step=== Execute retry (?<attempt>\\d+)$",
|
||||
"action": "captureAttemptNumber",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -153,7 +153,7 @@
|
||||
{
|
||||
"patternId": "IME-CACHE-MISS",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] Content cache miss for app \\(id = {GUID}, name = (?<name>.*?)\\), start downloading",
|
||||
"pattern": "^\\[Win32App\\] Content cache miss for app \\(id = {GUID}, name = (?<name>.*?)\\), start downloading",
|
||||
"action": "updateName",
|
||||
"description": "Captures 'Content cache miss for app (id = <guid>, name = <name>), start downloading...' (ContentManager.cs) as a secondary app-name source. Fires right before a real download begins, so it resolves names for exactly the apps that will produce download/install activity — a redundancy for IME-POLICIES-based naming after IME retired the old name-bearing processing lines.",
|
||||
"enabled": true
|
||||
@@ -161,7 +161,7 @@
|
||||
{
|
||||
"patternId": "IME-DECRYPT-FAILED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "Decryption is failed for appId {GUID}",
|
||||
"pattern": "^Decryption is failed for appId {GUID}",
|
||||
"action": "updateStateError",
|
||||
"description": "Detects when app content decryption fails, indicating a corrupted or tampered download.",
|
||||
"enabled": true
|
||||
@@ -169,7 +169,7 @@
|
||||
{
|
||||
"patternId": "IME-DETECTED-INSTALLED-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Detection state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DetectionState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}Installed\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Detection state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DetectionState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}Installed\"{1,2}",
|
||||
"action": "updateStateInstalled",
|
||||
"description": "Detects when an app's detection state transitions to Installed (new ReportingManager format). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
@@ -177,23 +177,15 @@
|
||||
{
|
||||
"patternId": "IME-DETECTED-INSTALLED-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[DetectionActionHandler\\] Detection for policy with id: {GUID} resulted in action status: Success and detection state: Detected",
|
||||
"pattern": "^\\[Win32App\\]\\[DetectionActionHandler\\] Detection for policy with id: {GUID} resulted in action status: Success and detection state: Detected",
|
||||
"action": "updateStateInstalled",
|
||||
"description": "Detects when an app's detection action handler reports successful detection (app is installed).",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"patternId": "IME-DETECTED-OLD",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Detected app {GUID} ",
|
||||
"action": "updateStateInstalled",
|
||||
"description": "Detects when an app is detected as installed (old log format). DISABLED 2026-08-17: the standalone 'Detected app <guid> ' line no longer exists in IME 1.97.107; this regex instead matched the 'Detected app ... and intent is to install, skip applicability check' line, colliding with IME-SKIP-INSTALL-DETECTED and firing updateStateInstalled and updateStateSkipped on the same physical line (see tasks/ime-v1-code-audit-2026-08-17.md).",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-DETECTION-RESULT",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\]\\[DetectionActionHandler\\] Detection for policy with id: (?<id>{GUID}) resulted in action status: Success and detection state: (?<detection>DetectedDifferentVersion|NotDetected|Detected)",
|
||||
"pattern": "^\\[Win32App\\]\\[DetectionActionHandler\\] Detection for policy with id: (?<id>{GUID}) resulted in action status: Success and detection state: (?<detection>DetectedDifferentVersion|NotDetected|Detected)",
|
||||
"action": "captureDetectionResult",
|
||||
"description": "Captures the detection rule result after IME runs detection for an app. 'Detected' means the detection rule matched (app is installed), 'NotDetected' means it did not, 'DetectedDifferentVersion' means a different version was found (supersedence / WinGet version mismatch). DetectedDifferentVersion must come first in the alternation so the unanchored regex does not mis-capture its 'Detected' prefix. Excludes [SimulatedDetectionActionHandler] which fires during applicability pre-checks.",
|
||||
"enabled": true
|
||||
@@ -201,23 +193,23 @@
|
||||
{
|
||||
"patternId": "IME-DO-TEL",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[DO TEL\\] = (?<doTelJson>\\{.*\\})",
|
||||
"pattern": "^\\[DO TEL\\] = (?<doTelJson>\\{.*\\})",
|
||||
"action": "updateDoTelemetry",
|
||||
"description": "Captures Delivery Optimization telemetry JSON after a DO download completes. Contains file size, peer caching stats, download mode, and duration.",
|
||||
"description": "Captures Delivery Optimization telemetry JSON after a DO download completes. Contains file size, peer caching stats, download mode, and duration. IME removed the [DO TEL] emitter in 1.104 (absent in 1.104.102.0 and 1.105.103.0 decompiles, present in 1.97.107.0); kept while 1.101/1.103 devices are in the fleet — retire once those versions are gone.",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"patternId": "IME-DO-TIMEOUT-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App DO\\] DO download(?=.*?\\bnot finished\\b)(?=.*?\\btimeout\\b).*?intunewin-bin_{GUID}",
|
||||
"pattern": "^\\[Win32App DO\\] DO downloading is not finished within timeout, jobId = [^,]{0,64}, fileId =.*?intunewin-bin_{GUID}",
|
||||
"action": "updateStatePostponed",
|
||||
"description": "Detects when a Delivery Optimization download times out for a specific app (identified by GUID in filename).",
|
||||
"description": "Detects when a Delivery Optimization download times out for a specific app (identified by GUID in the DO fileId). Source literal (ContentDownloaderDeliveryOptimization, identical IME 1.97-1.105): \"[Win32App DO] DO downloading is not finished within timeout, jobId = {0}, fileId = {1}\".",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"patternId": "IME-DO-TIMEOUT-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App DO\\] DO download(?=.*?\\bnot finished\\b)(?=.*?\\btimeout\\b)",
|
||||
"pattern": "^\\[Win32App DO\\] DO downloading is not finished within timeout",
|
||||
"action": "updateStatePostponed",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -228,7 +220,7 @@
|
||||
{
|
||||
"patternId": "IME-DOWNLOADING",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[StatusService\\] Downloading app \\(id = {GUID}.*?\\) via (?<tech>\\w+), bytes (?<bytes>\\w+)/(?<ofbytes>\\w+) for user",
|
||||
"pattern": "^\\[StatusService\\] Downloading app \\(id = {GUID}.*?\\) via (?<tech>\\w+), bytes (?<bytes>\\w+)/(?<ofbytes>\\w+) for user",
|
||||
"action": "updateStateDownloading",
|
||||
"description": "Captures app download progress including technology (DO/CDN), bytes downloaded, and total bytes.",
|
||||
"enabled": true
|
||||
@@ -236,7 +228,7 @@
|
||||
{
|
||||
"patternId": "IME-ENFORCEMENT-SUCCESS",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Execution state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:(?:\"{1,2}\\w+\"{1,2}|null),\"{1,2}NewValue\"{1,2}:\"{1,2}Success\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Execution state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:(?:\"{1,2}\\w+\"{1,2}|null),\"{1,2}NewValue\"{1,2}:\"{1,2}Success\"{1,2}",
|
||||
"action": "updateStateInstalled",
|
||||
"description": "Detects when an app's enforcement state transitions to Success in an Execution state report delta. OldValue is deliberately unpinned: besides InProgressDownloadCompleted it can be Error (retry succeeding in a later check-in), InProgressPendingReboot (post-reboot completion) or null (first write) — the previously pinned InProgressDownloadCompleted missed those legitimate success paths (IME 1.97 audit 2026-08-17). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
@@ -244,7 +236,7 @@
|
||||
{
|
||||
"patternId": "IME-ENFORCEMENT-SUCCESS-OLD",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Setting enforcementState as\\W+Success\\b",
|
||||
"pattern": "^\\[Win32App\\] Setting enforcementState as\\W+Success\\b",
|
||||
"action": "updateStateInstalled",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -255,7 +247,7 @@
|
||||
{
|
||||
"patternId": "IME-ERROR-DOWNLOAD",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Download state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:\"{1,2}InProgress\"{1,2},\"{1,2}NewValue\"{1,2}:\"{1,2}ErrorDownloadingContent\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Download state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:\"{1,2}InProgress\"{1,2},\"{1,2}NewValue\"{1,2}:\"{1,2}ErrorDownloadingContent\"{1,2}",
|
||||
"action": "updateStateError",
|
||||
"description": "Detects when an app's enforcement state transitions to ErrorDownloadingContent. Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
@@ -263,7 +255,7 @@
|
||||
{
|
||||
"patternId": "IME-ERROR-ENFORCEMENT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Setting enforcementState as\\W+Error\\b",
|
||||
"pattern": "^\\[Win32App\\] Setting enforcementState as\\W+Error\\b",
|
||||
"action": "updateStateError",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -274,7 +266,7 @@
|
||||
{
|
||||
"patternId": "IME-ERROR-NO-CONTENT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Didn't get content info from service",
|
||||
"pattern": "^\\[Win32App\\] Didn't get content info from service",
|
||||
"action": "updateStateError",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -285,7 +277,7 @@
|
||||
{
|
||||
"patternId": "IME-ERROR-REPORT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Execution state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:(?:\"{1,2}(?<from>\\w+)\"{1,2}|null),\"{1,2}NewValue\"{1,2}:\"{1,2}(?<to>\\w+)\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Execution state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:(?:\"{1,2}(?<from>\\w+)\"{1,2}|null),\"{1,2}NewValue\"{1,2}:\"{1,2}(?<to>\\w+)\"{1,2}",
|
||||
"action": "updateStateError",
|
||||
"parameters": {
|
||||
"checkTo": "true"
|
||||
@@ -296,7 +288,7 @@
|
||||
{
|
||||
"patternId": "IME-ERROR-TIMEOUT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Installation is timeout",
|
||||
"pattern": "^\\[Win32App\\] Installation is timeout",
|
||||
"action": "updateStateError",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -307,7 +299,7 @@
|
||||
{
|
||||
"patternId": "IME-ERROR-UNMAPPED-EXIT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Admin did NOT set mapping for lpExitCode\\W+(?<exitCode>\\d+) of app\\W+{GUID}",
|
||||
"pattern": "^\\[Win32App\\] Admin did NOT set mapping for lpExitCode\\W+(?<exitCode>\\d+) of app\\W+{GUID}",
|
||||
"action": "updateStateError",
|
||||
"description": "Detects when an app installer returned an exit code that has no mapping configured in Intune.",
|
||||
"enabled": true
|
||||
@@ -315,7 +307,7 @@
|
||||
{
|
||||
"patternId": "IME-ESP-APP-REGISTERED",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\]\\[EspManager\\] In EspPhase: \\w+\\. App {GUID} has been registered.*?App name: (?<name>.*?)$",
|
||||
"pattern": "^\\[Win32App\\]\\[EspManager\\] In EspPhase: \\w+\\. App {GUID} has been registered.*?App name: (?<name>.*?)$",
|
||||
"action": "updateName",
|
||||
"description": "Captures the ESP tracked-app registration line ('App <guid> has been registered[ for user <id>]. App name: <name>', EspManager.cs) as an app-name source. Primary replacement for the retired IME-UPDATE-NAME-1/2/3 whose emitters were removed with IME's old V1/V2 processing pipeline; also documents exactly which apps IME registered for ESP tracking. Category 'always' so names resolve even before the first ApplicationPoller EspPhase line is seen.",
|
||||
"enabled": true
|
||||
@@ -323,7 +315,7 @@
|
||||
{
|
||||
"patternId": "IME-ESP-PHASE",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] (?:In|The) EspPhase: (?<espPhase>\\w+)",
|
||||
"pattern": "^\\[Win32App\\] (?:In|The) EspPhase: (?<espPhase>\\w+)",
|
||||
"action": "espPhaseDetected",
|
||||
"description": "Detects ESP phase transitions reported by IME Win32App processing (e.g., DeviceSetup, AccountSetup).",
|
||||
"enabled": true
|
||||
@@ -331,7 +323,7 @@
|
||||
{
|
||||
"patternId": "IME-ESP-TRACK-STATUS",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[EspManager\\] Updating ESP tracked install status from (?<from>\\w+) to (?<to>\\w+) for application {GUID}",
|
||||
"pattern": "^\\[Win32App\\]\\[EspManager\\] Updating ESP tracked install status from (?<from>\\w+) to (?<to>\\w+) for application {GUID}",
|
||||
"action": "espTrackStatus",
|
||||
"description": "Captures ESP tracked install status transitions (e.g., InProgress to Installed) for a specific app. No trailing anchor: IME 1.97 (Dec 2025) appends ' with name: <appName>.' after the GUID (EspManager.cs), which broke the previous '\\.' suffix. 'from' can be the literal string 'null' on first tracking.",
|
||||
"enabled": true
|
||||
@@ -339,7 +331,7 @@
|
||||
{
|
||||
"patternId": "IME-EXITCODE",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] lpExitCode (?<exitCode>-?\\d+)$",
|
||||
"pattern": "^\\[Win32App\\] lpExitCode (?<exitCode>-?\\d+)$",
|
||||
"action": "captureExitCode",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -350,7 +342,7 @@
|
||||
{
|
||||
"patternId": "IME-EXITCODE-MAPPED-FAILED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] lpExitCode is defined as Failed$",
|
||||
"pattern": "^\\[Win32App\\] lpExitCode is defined as Failed$",
|
||||
"action": "updateStateError",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -361,7 +353,7 @@
|
||||
{
|
||||
"patternId": "IME-GRS-CLEARED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ExecutionActionHandler\\] Encountered early subgraph termination\\. Clearing GRS for app with id: {GUID} so it can be retried during the next check-in\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[ExecutionActionHandler\\] Encountered early subgraph termination\\. Clearing GRS for app with id: {GUID} so it can be retried during the next check-in\\.",
|
||||
"action": "updateStatePostponed",
|
||||
"description": "IME aborted the app's subgraph before enforcement finished and cleared its GRS entry so the app retries on the next check-in (ExecutionActionHandler, AppWorkload.log). Maps to Postponed: the install did not complete this pass but is scheduled to run again - previously this looked like an unexplained gap. Wording verified in IME 1.97 and 1.104.",
|
||||
"enabled": true
|
||||
@@ -369,7 +361,7 @@
|
||||
{
|
||||
"patternId": "IME-GRS-SKIP",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID} to (?:install|uninstall) is in GRS\\. The app will not be enforced\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID} to (?:install|uninstall) is in GRS\\. The app will not be enforced\\.",
|
||||
"action": "updateStatePostponed",
|
||||
"description": "GRS (Global Retry Schedule) skip: the app failed recently and IME will not re-enforce it this check-in ('App with id: <guid> to install is in GRS. The app will not be enforced.', EnforcementActionProcessor / MixedEnforcementActionProcessor). THE canonical reason an app silently does nothing for up to 24h - previously invisible. Lives in AppActionProcessor.log, which the agent tails since the 2026-08-18 release; wording verified in IME 1.97 and 1.104.",
|
||||
"enabled": true
|
||||
@@ -377,7 +369,7 @@
|
||||
{
|
||||
"patternId": "IME-HRESULT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] hResultFromWin32 (?<hresult>-?\\d+)$",
|
||||
"pattern": "^\\[Win32App\\] hResultFromWin32 (?<hresult>-?\\d+)$",
|
||||
"action": "captureHResult",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -388,7 +380,7 @@
|
||||
{
|
||||
"patternId": "IME-IMPERSONATION",
|
||||
"category": "always",
|
||||
"pattern": "After impersonation: (?<user>.*?)$",
|
||||
"pattern": "^After impersonation: (?<user>.*?)$",
|
||||
"action": "imeImpersonation",
|
||||
"description": "Detects when IME impersonates a user context for app installation.",
|
||||
"enabled": true
|
||||
@@ -396,7 +388,7 @@
|
||||
{
|
||||
"patternId": "IME-INSTALL-HANDLER",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ExecutionActionHandler\\] Handler invoked with execution type: Install for policy with id: {GUID}",
|
||||
"pattern": "^\\[Win32App\\]\\[ExecutionActionHandler\\] Handler invoked with execution type: Install for policy with id: {GUID}",
|
||||
"action": "updateStateInstalling",
|
||||
"description": "Detects when the IME execution handler starts installing an app.",
|
||||
"enabled": true
|
||||
@@ -404,7 +396,7 @@
|
||||
{
|
||||
"patternId": "IME-LAUNCH-INSTALLER",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Launch Win32AppInstaller in \\w+? session",
|
||||
"pattern": "^\\[Win32App\\] Launch Win32AppInstaller in \\w+? session",
|
||||
"action": "updateStateInstalling",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -415,7 +407,7 @@
|
||||
{
|
||||
"patternId": "IME-NO-ACTION",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ActionProcessor\\] No action required for app with id: {GUID}",
|
||||
"pattern": "^\\[Win32App\\]\\[ActionProcessor\\] No action required for app with id: {GUID}",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when IME determines no action is required for an app (already in desired state).",
|
||||
"enabled": true
|
||||
@@ -423,7 +415,7 @@
|
||||
{
|
||||
"patternId": "IME-NOT-APPLICABLE-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ApplicabilityActionHandler\\] Applicability check for policy with id: {GUID} resulted in action status: Success and applicability state: NotApplicable",
|
||||
"pattern": "^\\[Win32App\\]\\[ApplicabilityActionHandler\\] Applicability check for policy with id: {GUID} resulted in action status: Success and applicability state: NotApplicable",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's applicability check returns NotApplicable (requirements not met).",
|
||||
"enabled": true
|
||||
@@ -431,7 +423,7 @@
|
||||
{
|
||||
"patternId": "IME-NOT-APPLICABLE-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Applicability state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}ApplicabilityState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}ScriptRequirementRuleNotMet\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Applicability state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}ApplicabilityState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}ScriptRequirementRuleNotMet\"{1,2}",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's applicability state is set to ScriptRequirementRuleNotMet (PowerShell requirement script returned false). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
@@ -439,7 +431,7 @@
|
||||
{
|
||||
"patternId": "IME-NOT-APPLICABLE-3",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Desired state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DesiredState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}None\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Desired state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DesiredState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}None\"{1,2}",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's desired state is set to None (not targeted or not applicable). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
@@ -447,7 +439,7 @@
|
||||
{
|
||||
"patternId": "IME-NOT-APPLICABLE-4",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Desired state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DesiredState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}NotPresent\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Desired state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DesiredState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}NotPresent\"{1,2}",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's desired state is set to NotPresent (targeted for uninstall or not required). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
@@ -455,7 +447,7 @@
|
||||
{
|
||||
"patternId": "IME-NOT-TARGETED-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID}, targeted intent: RequiredInstall, and enforceability: Enforceable has projected enforcement classification: NotApplicableOrNotTargeted with desired state: None\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID}, targeted intent: RequiredInstall, and enforceability: Enforceable has projected enforcement classification: NotApplicableOrNotTargeted with desired state: None\\.",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app with RequiredInstall intent is classified as NotApplicableOrNotTargeted (new log format).",
|
||||
"enabled": true
|
||||
@@ -463,7 +455,7 @@
|
||||
{
|
||||
"patternId": "IME-NOT-TARGETED-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID}, effective intent: RequiredInstall, and enforceability: Enforceable has projected enforcement classification: NotApplicableOrNotTargeted with desired state: None\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID}, effective intent: RequiredInstall, and enforceability: Enforceable has projected enforcement classification: NotApplicableOrNotTargeted with desired state: None\\.",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app with effective RequiredInstall intent is classified as NotApplicableOrNotTargeted.",
|
||||
"enabled": true
|
||||
@@ -471,7 +463,7 @@
|
||||
{
|
||||
"patternId": "IME-POLICIES",
|
||||
"category": "currentPhase",
|
||||
"pattern": "Get policies =\\s*(?<policies>.*)",
|
||||
"pattern": "^Get policies =\\s*(?<policies>.*)",
|
||||
"action": "policiesDiscovered",
|
||||
"description": "Captures the JSON policies list discovered by IME, used to build the app inventory.",
|
||||
"enabled": true
|
||||
@@ -479,7 +471,7 @@
|
||||
{
|
||||
"patternId": "IME-RESTART-STOP-PROCESSING",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[V3Processor\\] A restart is required to continue processing apps\\. Stop processing\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[V3Processor\\] A restart is required to continue processing apps\\. Stop processing\\.",
|
||||
"action": "updateStatePostponed",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -490,39 +482,15 @@
|
||||
{
|
||||
"patternId": "IME-SESSION-CHANGE",
|
||||
"category": "always",
|
||||
"pattern": "OnSessionChange:? (?<change>.*?)$",
|
||||
"pattern": "^OnSessionChange:? (?<change>.*?)$",
|
||||
"action": "imeSessionChange",
|
||||
"description": "Detects Windows session change events (logon, logoff, lock, unlock) reported by IME.",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"patternId": "IME-SET-CURRENT-1",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] ProcessAppWithDependencies starts for {GUID} with name",
|
||||
"action": "setCurrentApp",
|
||||
"description": "Detects when IME begins processing an app with its dependency chain, setting it as the current app. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-SET-CURRENT-2",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] ExecManager: processing targeted app.*?\\bid\\W+{GUID}",
|
||||
"action": "setCurrentApp",
|
||||
"description": "Detects when the IME ExecManager starts processing a targeted app, setting it as current. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-SET-CURRENT-3",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] ===Step=== Start to Present app {GUID}",
|
||||
"action": "setCurrentApp",
|
||||
"description": "Detects the start of app presentation step in IME, setting the app as current. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-SET-CURRENT-4",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] SetCurrentDirectory:.*?\\\\{GUID}_\\d+",
|
||||
"pattern": "^\\[Win32App\\] SetCurrentDirectory:.*?\\\\{GUID}_\\d+",
|
||||
"action": "setCurrentApp",
|
||||
"description": "Detects when IME sets the working directory for an app's staging folder, setting it as current.",
|
||||
"enabled": true
|
||||
@@ -530,7 +498,7 @@
|
||||
{
|
||||
"patternId": "IME-SHUTDOWN",
|
||||
"category": "currentPhase",
|
||||
"pattern": "EMS Agent received shutdown signal",
|
||||
"pattern": "^EMS Agent received shutdown signal",
|
||||
"action": "updateStatePostponed",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -538,34 +506,10 @@
|
||||
"description": "Detects when the IME agent receives a shutdown signal, postponing the current app installation.",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"patternId": "IME-SKIP-APPLICABILITY-NOTMET",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] (?=.*?\\bapplicability\\b)(?=.*?NotMet\\b).*?\\bapp\\W+{GUID}\\b",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's applicability check returns NotMet (old log format with lookaheads). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-SKIP-DEPENDENCY-DETECT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] app\\W+{GUID}\\b.*?\\bis a dependency\\b.*?\\bDetect Only mode\\b.*?\\bfinished processing",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when a dependency app in Detect Only mode finishes processing (no install needed). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-SKIP-DETECTED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Detected app {GUID}.*?skip next step",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an already-detected app is skipped for further processing steps (old log format). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-SKIP-FILTERS",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[V3Processor\\] All apps in the subgraph are not applicable due to assignment filters\\. Skipping processing\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[V3Processor\\] All apps in the subgraph are not applicable due to assignment filters\\. Skipping processing\\.",
|
||||
"action": "updateStateSkipped",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -576,7 +520,7 @@
|
||||
{
|
||||
"patternId": "IME-SKIP-INSTALL-DETECTED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Detected app {GUID} and intent is to install, skip applicability check",
|
||||
"pattern": "^\\[Win32App\\] Detected app {GUID} and intent is to install, skip applicability check",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app targeted for install is already detected/installed, so the install is skipped (old log format).",
|
||||
"enabled": true
|
||||
@@ -584,23 +528,15 @@
|
||||
{
|
||||
"patternId": "IME-SKIP-UNINSTALL-NOTDETECTED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Not detected app {GUID} and intent is to un-install, skip applicability check",
|
||||
"pattern": "^\\[Win32App\\] Not detected app {GUID} and intent is to un-install, skip applicability check",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app targeted for uninstall is not detected, so the uninstall is skipped (old log format).",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"patternId": "IME-SKIP-UNINSTALL-NOTDETECTED-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] NOT detected app {GUID}.*?skip un-installation",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app is not detected and uninstall is skipped (alternate old log format). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-STARTED",
|
||||
"category": "always",
|
||||
"pattern": "EMS Agent Started",
|
||||
"pattern": "^EMS Agent Started",
|
||||
"action": "imeStarted",
|
||||
"description": "Detects when the Intune Management Extension (IME) agent starts up.",
|
||||
"enabled": true
|
||||
@@ -608,7 +544,7 @@
|
||||
{
|
||||
"patternId": "IME-STOP-SIGNAL",
|
||||
"category": "currentPhase",
|
||||
"pattern": "EMS Agent received stop signal",
|
||||
"pattern": "^EMS Agent received stop signal",
|
||||
"action": "updateStatePostponed",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
@@ -619,7 +555,7 @@
|
||||
{
|
||||
"patternId": "IME-SUBGRAPH",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[V3Processor\\] Processing subgraph with app ids: {GUID}",
|
||||
"pattern": "^\\[Win32App\\]\\[V3Processor\\] Processing subgraph with app ids: {GUID}",
|
||||
"action": "cancelStuckAndSetCurrent",
|
||||
"description": "Detects when V3Processor starts a new subgraph, cancelling any stuck app and setting the new app as current.",
|
||||
"enabled": true
|
||||
@@ -627,7 +563,7 @@
|
||||
{
|
||||
"patternId": "IME-TOKEN-FAILURE",
|
||||
"category": "always",
|
||||
"pattern": "Failed to get AAD token\\..*?errorCode = (?<errorCode>.*?)$",
|
||||
"pattern": "^Failed to get AAD token\\..*?errorCode = (?<errorCode>.*?)$",
|
||||
"action": "imeTokenFailure",
|
||||
"description": "IME token acquisition failure ('Failed to get AAD token. len = N using client id ... and resource id ..., errorCode = N', IntuneTokenManager, IntuneManagementExtension.log). This line appears ROUTINELY during the device phase (no user token exists yet), so the agent holds the failure pending: any IME-TOKEN-SUCCESS match clears it, and the ime_token_failure Warning fires only when no success follows for the whole 30-min grace window (then deduped to one Warning per distinct errorCode). A genuine auth outage (WAM broken, conditional access, network) runs long enough to hit the window. Verified against IME 1.97 and 1.104 decompiled sources.",
|
||||
"enabled": true
|
||||
@@ -635,7 +571,7 @@
|
||||
{
|
||||
"patternId": "IME-TOKEN-SUCCESS",
|
||||
"category": "always",
|
||||
"pattern": "Successfully get the token",
|
||||
"pattern": "^Successfully get the token",
|
||||
"action": "espPhaseDetected",
|
||||
"parameters": {
|
||||
"phase": "AccountSetup"
|
||||
@@ -643,34 +579,10 @@
|
||||
"description": "Detects successful token acquisition, indicating the AccountSetup ESP phase has started.",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"patternId": "IME-UPDATE-NAME-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] ExecManager: processing targeted app.*?name\\W+(?<name>.*?)\\W+id\\W+{GUID}",
|
||||
"action": "updateName",
|
||||
"description": "Captures the app name from ExecManager processing log (old format: name before id). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-UPDATE-NAME-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] ProcessAppWithDependencies starts for {GUID} with name (?<name>.*?)$",
|
||||
"action": "updateName",
|
||||
"description": "Captures the app name from ProcessAppWithDependencies log entry. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-UPDATE-NAME-3",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Processing app \\(id={GUID}, name = (?<name>.*?)\\)",
|
||||
"action": "updateName",
|
||||
"description": "Captures the app name from the Processing app log entry. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"patternId": "IME-USER-SESSION-COMPLETED",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] \\.+ Completed user session \\d+, userId: (?<userId>[a-f0-9\\-]+), userSID: (?<userSid>S-[\\d\\-]+) \\.+",
|
||||
"pattern": "^\\[Win32App\\] \\.+ Completed user session \\d+, userId: (?<userId>[a-f0-9\\-]+), userSID: (?<userSid>S-[\\d\\-]+) \\.+",
|
||||
"action": "enrollmentCompleted",
|
||||
"description": "Detects when IME completes a user session, indicating enrollment has finished for that user.",
|
||||
"enabled": true
|
||||
@@ -678,7 +590,7 @@
|
||||
{
|
||||
"patternId": "IME-USER-SESSION-ZERO-APPS",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] Get 0 apps for user session (?<sessionId>\\d+), user id = (?<userId>[a-f0-9\\-]+)",
|
||||
"pattern": "^\\[Win32App\\] Get 0 apps for user session (?<sessionId>\\d+), user id = (?<userId>[a-f0-9\\-]+)",
|
||||
"action": "userSessionZeroApps",
|
||||
"description": "Detects when IME enumerated a user session and found zero targeted Win32 apps. IME then skips its 'Completed user session' line entirely (the zero-app branch in ApplicationPoller continues before that log statement), so this line is the only evidence that user-phase enforcement finished on a device with no user-targeted apps. Treated as equivalent to a completed user session.",
|
||||
"enabled": true
|
||||
@@ -686,7 +598,7 @@
|
||||
{
|
||||
"patternId": "IME-WIN32-STATE-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Got InstanceID: Win32App_{GUID}_\\d+, installationStateString: (?<state>\\d)",
|
||||
"pattern": "^\\[Win32App\\] Got InstanceID: Win32App_{GUID}_\\d+, installationStateString: (?<state>\\d)",
|
||||
"action": "updateWin32AppState",
|
||||
"description": "Captures the Win32 app installation state string from instance ID tracking (old log format).",
|
||||
"enabled": true
|
||||
@@ -694,7 +606,7 @@
|
||||
{
|
||||
"patternId": "IME-WIN32-STATE-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] (?:Got|Set) (?:ESP )?win32App state (?<state>\\w+) for {GUID}",
|
||||
"pattern": "^\\[Win32App\\] (?:Got|Set) (?:ESP )?win32App state (?<state>\\w+) for {GUID}",
|
||||
"action": "updateWin32AppState",
|
||||
"description": "Captures the Win32 app ESP state (e.g., Installed, InProgress) for a specific app.",
|
||||
"enabled": true
|
||||
@@ -702,7 +614,7 @@
|
||||
{
|
||||
"patternId": "IME-WINGET-ERROR",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID}.*?- Operation Phase = Error",
|
||||
"pattern": "^\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID}.*?- Operation Phase = Error",
|
||||
"action": "updateStateError",
|
||||
"description": "Marks a WinGet (new Store) app as failed when its operation progress reports phase Error (WinGetOperationInstallPhase: Downloading/Installing/Queued/Finished/Error). Before this pattern only the Installing phase was matched, so WinGet failures never surfaced as app errors (IME 1.97 audit 2026-08-17).",
|
||||
"enabled": true
|
||||
@@ -710,7 +622,7 @@
|
||||
{
|
||||
"patternId": "IME-WINGET-INSTALLING-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID} and user = .*?- Operation Phase = Installing",
|
||||
"pattern": "^\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID} and user = .*?- Operation Phase = Installing",
|
||||
"action": "updateStateInstalling",
|
||||
"description": "Detects WinGet app installation progress with Operation Phase = Installing (format variant 1).",
|
||||
"enabled": true
|
||||
@@ -718,7 +630,7 @@
|
||||
{
|
||||
"patternId": "IME-WINGET-INSTALLING-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID}.*?- Operation Phase = Installing",
|
||||
"pattern": "^\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID}.*?- Operation Phase = Installing",
|
||||
"action": "updateStateInstalling",
|
||||
"description": "Detects WinGet app installation progress with Operation Phase = Installing (format variant 2).",
|
||||
"enabled": true
|
||||
@@ -726,7 +638,7 @@
|
||||
{
|
||||
"patternId": "PS-AGENT-ERROR",
|
||||
"category": "always",
|
||||
"pattern": "error from script =(?<output>[\\s\\S]*)",
|
||||
"pattern": "^error from script =(?<output>[\\s\\S]*)",
|
||||
"action": "scriptOutput",
|
||||
"parameters": {
|
||||
"scriptType": "platform",
|
||||
@@ -738,7 +650,7 @@
|
||||
{
|
||||
"patternId": "PS-AGENT-EXITCODE",
|
||||
"category": "always",
|
||||
"pattern": "Powershell exit code is (?<exitCode>-?\\d+)",
|
||||
"pattern": "^Powershell exit code is (?<exitCode>-?\\d+)",
|
||||
"action": "scriptExitCode",
|
||||
"parameters": {
|
||||
"scriptType": "platform"
|
||||
@@ -749,7 +661,7 @@
|
||||
{
|
||||
"patternId": "PS-AGENT-INVOCATION",
|
||||
"category": "always",
|
||||
"pattern": "ExecutorLog AgentExecutor gets invoked",
|
||||
"pattern": "^ExecutorLog AgentExecutor gets invoked",
|
||||
"action": "resetPlatformScriptContext",
|
||||
"description": "Marks the start of a new AgentExecutor.exe invocation. AgentExecutor hosts BOTH platform scripts (Policies\\Scripts\\<id>.ps1) and proactive-remediation scripts (IMECache\\HealthScripts\\<id>\\detect.ps1) and writes their lines interleaved into the shared AgentExecutor.log. The policyId-less 'Powershell exit code is N' / 'write output done' lines are otherwise routed to the last-started platform script via a sticky pointer, so a remediation invocation (whose start line does not match PS-AGENT-SCRIPT-START) leaks its exit/output into that platform script's result. This pattern resets the platform-script line-capture context at each invocation boundary so only the matching platform invocation captures its own exit/output.",
|
||||
"enabled": true
|
||||
@@ -757,7 +669,7 @@
|
||||
{
|
||||
"patternId": "PS-AGENT-OUTPUT",
|
||||
"category": "always",
|
||||
"pattern": "write output done\\. output = (?<output>[\\s\\S]*?), error = (?<error>[\\s\\S]*)",
|
||||
"pattern": "^write output done\\. output = (?<output>[\\s\\S]*?), error = (?<error>[\\s\\S]*)",
|
||||
"action": "scriptOutput",
|
||||
"parameters": {
|
||||
"scriptType": "platform"
|
||||
@@ -768,7 +680,7 @@
|
||||
{
|
||||
"patternId": "PS-AGENT-SCRIPT-START",
|
||||
"category": "always",
|
||||
"pattern": "Adding argument powershell with value C:\\\\.*\\\\Policies\\\\Scripts\\\\[a-z0-9\\-]+_(?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\.ps1",
|
||||
"pattern": "^Adding argument powershell with value C:\\\\.*\\\\Policies\\\\Scripts\\\\[a-z0-9\\-]+_(?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\.ps1",
|
||||
"action": "scriptStarted",
|
||||
"parameters": {
|
||||
"scriptType": "platform",
|
||||
@@ -780,7 +692,7 @@
|
||||
{
|
||||
"patternId": "PS-SCRIPT-CONTEXT",
|
||||
"category": "always",
|
||||
"pattern": "Launch powershell executor in (?<context>machine|user) session",
|
||||
"pattern": "^Launch powershell executor in (?<context>machine|user) session",
|
||||
"action": "scriptContext",
|
||||
"description": "Captures whether a PowerShell script runs in machine (System) or user session context.",
|
||||
"enabled": true
|
||||
@@ -788,7 +700,7 @@
|
||||
{
|
||||
"patternId": "PS-SCRIPT-EXITCODE",
|
||||
"category": "always",
|
||||
"pattern": "Powershell execution is done, exitCode = (?<exitCode>-?\\d+)",
|
||||
"pattern": "^Powershell execution is done, exitCode = (?<exitCode>-?\\d+)",
|
||||
"action": "scriptExitCode",
|
||||
"description": "Captures the PowerShell exit code when script execution completes in the IME log.",
|
||||
"enabled": true
|
||||
@@ -796,7 +708,7 @@
|
||||
{
|
||||
"patternId": "PS-SCRIPT-GENERATED",
|
||||
"category": "always",
|
||||
"pattern": "Script file C:\\\\.*\\\\Policies\\\\Scripts\\\\[a-z0-9\\-]+_(?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\.ps1 is generated",
|
||||
"pattern": "^Script file C:\\\\.*\\\\Policies\\\\Scripts\\\\[a-z0-9\\-]+_(?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})\\.ps1 is generated",
|
||||
"action": "scriptStarted",
|
||||
"parameters": {
|
||||
"scriptType": "platform"
|
||||
@@ -807,7 +719,7 @@
|
||||
{
|
||||
"patternId": "PS-SCRIPT-RESULT",
|
||||
"category": "always",
|
||||
"pattern": "\\[PowerShell\\] User Id = [a-z0-9\\-]+, Policy id = (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}), policy result = (?<result>\\w+)",
|
||||
"pattern": "^\\[PowerShell\\] User Id = [a-z0-9\\-]+, Policy id = (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}), policy result = (?<result>\\w+)",
|
||||
"action": "scriptCompleted",
|
||||
"parameters": {
|
||||
"scriptType": "platform"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "HS-COMPLIANCE-POST",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] the (?<part>post) detection script result for (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) is (?<compliance>True|False)",
|
||||
"pattern": "^\\[HS\\] the (?<part>post) detection script result for (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) is (?<compliance>True|False)",
|
||||
"action": "healthScriptDetectionResult",
|
||||
"parameters": { "scriptType": "remediation" },
|
||||
"description": "Post-remediation detection result, current wording. IME 1.97 (Dec 2025) replaced the old '[HS] the post-remdiation detection script compliance result for ...' line (still covered by HS-COMPLIANCE for older logs) with '[HS] the post detection script result for <guid> is <True|False>' (ScriptProcessorV2.cs:721). The literal word 'post' in the line is captured as the part group so the handler classifies it as the post-detection phase.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "HS-COMPLIANCE",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] the (?<part>pre|post)-rem(?:e)?diation detection script compliance result for (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) is (?<compliance>True|False)",
|
||||
"pattern": "^\\[HS\\] the (?<part>pre|post)-rem(?:e)?diation detection script compliance result for (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) is (?<compliance>True|False)",
|
||||
"action": "healthScriptDetectionResult",
|
||||
"parameters": { "scriptType": "remediation" },
|
||||
"description": "Early-signal fallback for health-script detection / post-detection results. Microsoft IME emits this line immediately after each detection script runs, ~30-90 seconds before the consolidated [HS] new result = JSON line is logged (which only fires after IME's batch-send to the Microsoft service). Short Autopilot enrollments often end inside that gap, so this pattern guarantees at least the compliance verdict is captured. The full data version arrives later via HS-NEW-RESULT and replaces this sparse entry through the UI dedupe (dataCompleteness scoring). Tolerates both Microsoft's historical typo 'remdiation' and a future corrected 'remediation'. NOTE: IME 1.97 (Dec 2025) no longer emits the post- variant of this line; the post case is covered by HS-COMPLIANCE-POST.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "HS-EXITCODE",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] exit code of the script is (?<exitCode>-?\\d+)",
|
||||
"pattern": "^\\[HS\\] exit code of the script is (?<exitCode>-?\\d+)",
|
||||
"action": "scriptExitCode",
|
||||
"parameters": { "scriptType": "remediation" },
|
||||
"description": "Captures the exit code of a health-script execution. Feeds the early-signal slot consumed by HS-COMPLIANCE so the user sees the real exit code (not just the inferred 0/1) before HS-NEW-RESULT arrives.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "HS-NEW-RESULT",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] new result = (?<json>\\{[\\s\\S]*\\})\\s*$",
|
||||
"pattern": "^\\[HS\\] new result = (?<json>\\{[\\s\\S]*\\})\\s*$",
|
||||
"action": "healthScriptResult",
|
||||
"parameters": { "scriptType": "remediation" },
|
||||
"description": "Atomic final JSON of a health script run, covering all three phases (pre-detection / remediation / post-detection) emitted by IME after the policy completes. Single source of truth for health-script outcomes — replaces the line-by-line HS-COMPLIANCE/HS-EXITCODE/HS-STDOUT/HS-STDERR patterns. The end-anchor and [\\s\\S] character class are defensive against future format changes and multi-line CMTrace buffering.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "HS-RUN-CONTEXT",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] The policy needs be run as (?<context>System|User)",
|
||||
"pattern": "^\\[HS\\] The policy needs be run as (?<context>System|User)",
|
||||
"action": "scriptContext",
|
||||
"parameters": { "scriptType": "remediation" },
|
||||
"description": "Captures the execution context (System or User) for a health-script. Feeds the early-signal slot consumed by HS-COMPLIANCE so context is visible immediately rather than waiting for HS-NEW-RESULT (which exposes RunAsAccount).",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "HS-SCRIPT-START",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] ProcessScript PolicyId: (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) PolicyType: (?<policyType>\\d+)",
|
||||
"pattern": "^\\[HS\\] ProcessScript PolicyId: (?<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}) PolicyType: (?<policyType>\\d+)",
|
||||
"action": "scriptStarted",
|
||||
"parameters": { "scriptType": "remediation" },
|
||||
"description": "Detects when a Proactive Remediation (Health Script) starts processing. Captures PolicyId and PolicyType (6 = remediation). Drives the live script_started event for UI progress indication; the consolidated final result for the same PolicyId arrives later via HS-NEW-RESULT.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "HS-STDERR",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] err output = (?<output>.*)",
|
||||
"pattern": "^\\[HS\\] err output = (?<output>.*)",
|
||||
"action": "scriptOutput",
|
||||
"parameters": { "scriptType": "remediation", "outputType": "stderr" },
|
||||
"description": "Captures standard error from a health-script execution. Feeds the early-signal slot consumed by HS-COMPLIANCE so stderr is visible immediately rather than waiting for HS-NEW-RESULT.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "HS-STDOUT",
|
||||
"category": "always",
|
||||
"pattern": "\\[HS\\] std output = (?<output>.*)",
|
||||
"pattern": "^\\[HS\\] std output = (?<output>.*)",
|
||||
"action": "scriptOutput",
|
||||
"parameters": { "scriptType": "remediation", "outputType": "stdout" },
|
||||
"description": "Captures standard output from a health-script execution. Feeds the early-signal slot consumed by HS-COMPLIANCE so the user sees stdout immediately rather than waiting for HS-NEW-RESULT.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-AGENT-VERSION",
|
||||
"category": "always",
|
||||
"pattern": "Agent version is: (?<agentVersion>[\\d.]+)",
|
||||
"pattern": "^Agent version is: (?<agentVersion>[\\d.]+)",
|
||||
"action": "imeAgentVersion",
|
||||
"description": "Captures the Intune Management Extension agent version number.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ALREADY-DETECTED-1",
|
||||
"category": "otherPhases",
|
||||
"pattern": "\\[Win32App\\] Completed detectionManager SideCar\\w+?DetectionManager, applicationDetectedByCurrentRule: True",
|
||||
"pattern": "^\\[Win32App\\] Completed detectionManager SideCar\\w+?DetectionManager, applicationDetectedByCurrentRule: True",
|
||||
"action": "ignoreCompletedApp",
|
||||
"description": "Detects when an app from a previous ESP phase is detected as already installed, so it should be ignored in the current phase.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ALREADY-DETECTED-2",
|
||||
"category": "otherPhases",
|
||||
"pattern": "\\[Win32App\\] Setting enforcementState as\\W+Success\\b",
|
||||
"pattern": "^\\[Win32App\\] Setting enforcementState as\\W+Success\\b",
|
||||
"action": "ignoreCompletedApp",
|
||||
"description": "Detects enforcement success in a non-current phase, indicating an app completed in a previous phase should be ignored.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-APP-TYPE-MSI",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\].*?\\bMSI App (?<id>{GUID})",
|
||||
"pattern": "^\\[Win32App\\].*?\\bMSI App (?<id>{GUID})",
|
||||
"action": "captureAppTypeMsi",
|
||||
"description": "Detects an MSI-based Win32 app via the 'MSI App <guid>' phrase in the ESP retry configuration line. Marks AppType as 'MSI' for that app id.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-APP-TYPE-WINGET",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\]\\[WinGetApp\\].*?app with id: (?<id>{GUID})",
|
||||
"pattern": "^\\[Win32App\\]\\[WinGetApp\\].*?app with id: (?<id>{GUID})",
|
||||
"action": "captureAppTypeWinGet",
|
||||
"description": "Detects a WinGet-backed Store app by any [Win32App][WinGetApp] handler log line (Detection/Applicability/Operation). Marks AppType as 'WinGet' for that app id.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-APP-VERSION",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Detection state for app with id: (?<id>{GUID}).*?\"DetectedIdentityVersion\":\\{[^}]*?\"NewValue\":\"(?<appVersion>[^\"]+)\"",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Detection state for app with id: (?<id>{GUID}).*?\"DetectedIdentityVersion\":\\{(?:\"OldValue\":[^,}]{0,64},)?\"NewValue\":\"(?<appVersion>[^\"]+)\"",
|
||||
"action": "captureAppVersion",
|
||||
"description": "Captures the product version IME detected after an app install (e.g. \"11.2.1787.0\"). Source: [ReportingManager] Detection state update with DetectedIdentityVersion.NewValue. Used by App Dashboard to show version-level failure breakdowns.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ATTEMPT-NUMBER",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] ===Step=== Execute retry (?<attempt>\\d+)$",
|
||||
"pattern": "^\\[Win32App\\] ===Step=== Execute retry (?<attempt>\\d+)$",
|
||||
"action": "captureAttemptNumber",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-CACHE-MISS",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] Content cache miss for app \\(id = {GUID}, name = (?<name>.*?)\\), start downloading",
|
||||
"pattern": "^\\[Win32App\\] Content cache miss for app \\(id = {GUID}, name = (?<name>.*?)\\), start downloading",
|
||||
"action": "updateName",
|
||||
"description": "Captures 'Content cache miss for app (id = <guid>, name = <name>), start downloading...' (ContentManager.cs) as a secondary app-name source. Fires right before a real download begins, so it resolves names for exactly the apps that will produce download/install activity — a redundancy for IME-POLICIES-based naming after IME retired the old name-bearing processing lines.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-DECRYPT-FAILED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "Decryption is failed for appId {GUID}",
|
||||
"pattern": "^Decryption is failed for appId {GUID}",
|
||||
"action": "updateStateError",
|
||||
"description": "Detects when app content decryption fails, indicating a corrupted or tampered download.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-DETECTED-INSTALLED-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Detection state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DetectionState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}Installed\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Detection state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DetectionState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}Installed\"{1,2}",
|
||||
"action": "updateStateInstalled",
|
||||
"description": "Detects when an app's detection state transitions to Installed (new ReportingManager format). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-DETECTED-INSTALLED-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[DetectionActionHandler\\] Detection for policy with id: {GUID} resulted in action status: Success and detection state: Detected",
|
||||
"pattern": "^\\[Win32App\\]\\[DetectionActionHandler\\] Detection for policy with id: {GUID} resulted in action status: Success and detection state: Detected",
|
||||
"action": "updateStateInstalled",
|
||||
"description": "Detects when an app's detection action handler reports successful detection (app is installed).",
|
||||
"enabled": true
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-DETECTED-OLD",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Detected app {GUID} ",
|
||||
"action": "updateStateInstalled",
|
||||
"description": "Detects when an app is detected as installed (old log format). DISABLED 2026-08-17: the standalone 'Detected app <guid> ' line no longer exists in IME 1.97.107; this regex instead matched the 'Detected app ... and intent is to install, skip applicability check' line, colliding with IME-SKIP-INSTALL-DETECTED and firing updateStateInstalled and updateStateSkipped on the same physical line (see tasks/ime-v1-code-audit-2026-08-17.md).",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-DETECTION-RESULT",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\]\\[DetectionActionHandler\\] Detection for policy with id: (?<id>{GUID}) resulted in action status: Success and detection state: (?<detection>DetectedDifferentVersion|NotDetected|Detected)",
|
||||
"pattern": "^\\[Win32App\\]\\[DetectionActionHandler\\] Detection for policy with id: (?<id>{GUID}) resulted in action status: Success and detection state: (?<detection>DetectedDifferentVersion|NotDetected|Detected)",
|
||||
"action": "captureDetectionResult",
|
||||
"description": "Captures the detection rule result after IME runs detection for an app. 'Detected' means the detection rule matched (app is installed), 'NotDetected' means it did not, 'DetectedDifferentVersion' means a different version was found (supersedence / WinGet version mismatch). DetectedDifferentVersion must come first in the alternation so the unanchored regex does not mis-capture its 'Detected' prefix. Excludes [SimulatedDetectionActionHandler] which fires during applicability pre-checks.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-DO-TEL",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[DO TEL\\] = (?<doTelJson>\\{.*\\})",
|
||||
"pattern": "^\\[DO TEL\\] = (?<doTelJson>\\{.*\\})",
|
||||
"action": "updateDoTelemetry",
|
||||
"description": "Captures Delivery Optimization telemetry JSON after a DO download completes. Contains file size, peer caching stats, download mode, and duration.",
|
||||
"description": "Captures Delivery Optimization telemetry JSON after a DO download completes. Contains file size, peer caching stats, download mode, and duration. IME removed the [DO TEL] emitter in 1.104 (absent in 1.104.102.0 and 1.105.103.0 decompiles, present in 1.97.107.0); kept while 1.101/1.103 devices are in the fleet — retire once those versions are gone.",
|
||||
"enabled": true
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-DO-TIMEOUT-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App DO\\] DO download(?=.*?\\bnot finished\\b)(?=.*?\\btimeout\\b).*?intunewin-bin_{GUID}",
|
||||
"pattern": "^\\[Win32App DO\\] DO downloading is not finished within timeout, jobId = [^,]{0,64}, fileId =.*?intunewin-bin_{GUID}",
|
||||
"action": "updateStatePostponed",
|
||||
"description": "Detects when a Delivery Optimization download times out for a specific app (identified by GUID in filename).",
|
||||
"description": "Detects when a Delivery Optimization download times out for a specific app (identified by GUID in the DO fileId). Source literal (ContentDownloaderDeliveryOptimization, identical IME 1.97-1.105): \"[Win32App DO] DO downloading is not finished within timeout, jobId = {0}, fileId = {1}\".",
|
||||
"enabled": true
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-DO-TIMEOUT-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App DO\\] DO download(?=.*?\\bnot finished\\b)(?=.*?\\btimeout\\b)",
|
||||
"pattern": "^\\[Win32App DO\\] DO downloading is not finished within timeout",
|
||||
"action": "updateStatePostponed",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects when a Delivery Optimization download times out (generic, uses current app context).",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-DOWNLOADING",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[StatusService\\] Downloading app \\(id = {GUID}.*?\\) via (?<tech>\\w+), bytes (?<bytes>\\w+)/(?<ofbytes>\\w+) for user",
|
||||
"pattern": "^\\[StatusService\\] Downloading app \\(id = {GUID}.*?\\) via (?<tech>\\w+), bytes (?<bytes>\\w+)/(?<ofbytes>\\w+) for user",
|
||||
"action": "updateStateDownloading",
|
||||
"description": "Captures app download progress including technology (DO/CDN), bytes downloaded, and total bytes.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ENFORCEMENT-SUCCESS-OLD",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Setting enforcementState as\\W+Success\\b",
|
||||
"pattern": "^\\[Win32App\\] Setting enforcementState as\\W+Success\\b",
|
||||
"action": "updateStateInstalled",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects when enforcement state is set to Success for the current app (old log format).",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ENFORCEMENT-SUCCESS",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Execution state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:(?:\"{1,2}\\w+\"{1,2}|null),\"{1,2}NewValue\"{1,2}:\"{1,2}Success\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Execution state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:(?:\"{1,2}\\w+\"{1,2}|null),\"{1,2}NewValue\"{1,2}:\"{1,2}Success\"{1,2}",
|
||||
"action": "updateStateInstalled",
|
||||
"description": "Detects when an app's enforcement state transitions to Success in an Execution state report delta. OldValue is deliberately unpinned: besides InProgressDownloadCompleted it can be Error (retry succeeding in a later check-in), InProgressPendingReboot (post-reboot completion) or null (first write) — the previously pinned InProgressDownloadCompleted missed those legitimate success paths (IME 1.97 audit 2026-08-17). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ERROR-DOWNLOAD",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Download state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:\"{1,2}InProgress\"{1,2},\"{1,2}NewValue\"{1,2}:\"{1,2}ErrorDownloadingContent\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Download state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:\"{1,2}InProgress\"{1,2},\"{1,2}NewValue\"{1,2}:\"{1,2}ErrorDownloadingContent\"{1,2}",
|
||||
"action": "updateStateError",
|
||||
"description": "Detects when an app's enforcement state transitions to ErrorDownloadingContent. Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ERROR-ENFORCEMENT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Setting enforcementState as\\W+Error\\b",
|
||||
"pattern": "^\\[Win32App\\] Setting enforcementState as\\W+Error\\b",
|
||||
"action": "updateStateError",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects when enforcement state is set to Error for the current app (old log format).",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ERROR-NO-CONTENT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Didn't get content info from service",
|
||||
"pattern": "^\\[Win32App\\] Didn't get content info from service",
|
||||
"action": "updateStateError",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects when IME cannot retrieve content info from the Intune service for the current app.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ERROR-REPORT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Execution state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:(?:\"{1,2}(?<from>\\w+)\"{1,2}|null),\"{1,2}NewValue\"{1,2}:\"{1,2}(?<to>\\w+)\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Execution state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}EnforcementState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:(?:\"{1,2}(?<from>\\w+)\"{1,2}|null),\"{1,2}NewValue\"{1,2}:\"{1,2}(?<to>\\w+)\"{1,2}",
|
||||
"action": "updateStateError",
|
||||
"parameters": {
|
||||
"checkTo": "true"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ERROR-TIMEOUT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Installation is timeout",
|
||||
"pattern": "^\\[Win32App\\] Installation is timeout",
|
||||
"action": "updateStateError",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects when an app installation times out.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ERROR-UNMAPPED-EXIT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Admin did NOT set mapping for lpExitCode\\W+(?<exitCode>\\d+) of app\\W+{GUID}",
|
||||
"pattern": "^\\[Win32App\\] Admin did NOT set mapping for lpExitCode\\W+(?<exitCode>\\d+) of app\\W+{GUID}",
|
||||
"action": "updateStateError",
|
||||
"description": "Detects when an app installer returned an exit code that has no mapping configured in Intune.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ESP-APP-REGISTERED",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\]\\[EspManager\\] In EspPhase: \\w+\\. App {GUID} has been registered.*?App name: (?<name>.*?)$",
|
||||
"pattern": "^\\[Win32App\\]\\[EspManager\\] In EspPhase: \\w+\\. App {GUID} has been registered.*?App name: (?<name>.*?)$",
|
||||
"action": "updateName",
|
||||
"description": "Captures the ESP tracked-app registration line ('App <guid> has been registered[ for user <id>]. App name: <name>', EspManager.cs) as an app-name source. Primary replacement for the retired IME-UPDATE-NAME-1/2/3 whose emitters were removed with IME's old V1/V2 processing pipeline; also documents exactly which apps IME registered for ESP tracking. Category 'always' so names resolve even before the first ApplicationPoller EspPhase line is seen.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ESP-PHASE",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] (?:In|The) EspPhase: (?<espPhase>\\w+)",
|
||||
"pattern": "^\\[Win32App\\] (?:In|The) EspPhase: (?<espPhase>\\w+)",
|
||||
"action": "espPhaseDetected",
|
||||
"description": "Detects ESP phase transitions reported by IME Win32App processing (e.g., DeviceSetup, AccountSetup).",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-ESP-TRACK-STATUS",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[EspManager\\] Updating ESP tracked install status from (?<from>\\w+) to (?<to>\\w+) for application {GUID}",
|
||||
"pattern": "^\\[Win32App\\]\\[EspManager\\] Updating ESP tracked install status from (?<from>\\w+) to (?<to>\\w+) for application {GUID}",
|
||||
"action": "espTrackStatus",
|
||||
"description": "Captures ESP tracked install status transitions (e.g., InProgress to Installed) for a specific app. No trailing anchor: IME 1.97 (Dec 2025) appends ' with name: <appName>.' after the GUID (EspManager.cs), which broke the previous '\\.' suffix. 'from' can be the literal string 'null' on first tracking.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-EXITCODE-MAPPED-FAILED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] lpExitCode is defined as Failed$",
|
||||
"pattern": "^\\[Win32App\\] lpExitCode is defined as Failed$",
|
||||
"action": "updateStateError",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Marks the current app as failed when its exit code maps to Failed via the admin-defined return-code table ('[Win32App] lpExitCode is defined as <Failed|Success|SoftReboot|HardReboot|Retry>', Win32AppInstaller.cs). This is the NORMAL mapped-exit-code outcome; the previously matched 'Setting enforcementState as ... Error' line only fires on the rare no-mapping fallback path. The numeric exit code is captured separately by IME-EXITCODE, which precedes this line.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-EXITCODE",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] lpExitCode (?<exitCode>-?\\d+)$",
|
||||
"pattern": "^\\[Win32App\\] lpExitCode (?<exitCode>-?\\d+)$",
|
||||
"action": "captureExitCode",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-GRS-CLEARED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ExecutionActionHandler\\] Encountered early subgraph termination\\. Clearing GRS for app with id: {GUID} so it can be retried during the next check-in\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[ExecutionActionHandler\\] Encountered early subgraph termination\\. Clearing GRS for app with id: {GUID} so it can be retried during the next check-in\\.",
|
||||
"action": "updateStatePostponed",
|
||||
"description": "IME aborted the app's subgraph before enforcement finished and cleared its GRS entry so the app retries on the next check-in (ExecutionActionHandler, AppWorkload.log). Maps to Postponed: the install did not complete this pass but is scheduled to run again - previously this looked like an unexplained gap. Wording verified in IME 1.97 and 1.104.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-GRS-SKIP",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID} to (?:install|uninstall) is in GRS\\. The app will not be enforced\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID} to (?:install|uninstall) is in GRS\\. The app will not be enforced\\.",
|
||||
"action": "updateStatePostponed",
|
||||
"description": "GRS (Global Retry Schedule) skip: the app failed recently and IME will not re-enforce it this check-in ('App with id: <guid> to install is in GRS. The app will not be enforced.', EnforcementActionProcessor / MixedEnforcementActionProcessor). THE canonical reason an app silently does nothing for up to 24h - previously invisible. Lives in AppActionProcessor.log, which the agent tails since the 2026-08-18 release; wording verified in IME 1.97 and 1.104.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-HRESULT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] hResultFromWin32 (?<hresult>-?\\d+)$",
|
||||
"pattern": "^\\[Win32App\\] hResultFromWin32 (?<hresult>-?\\d+)$",
|
||||
"action": "captureHResult",
|
||||
"parameters": {
|
||||
"useCurrentApp": "true"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-IMPERSONATION",
|
||||
"category": "always",
|
||||
"pattern": "After impersonation: (?<user>.*?)$",
|
||||
"pattern": "^After impersonation: (?<user>.*?)$",
|
||||
"action": "imeImpersonation",
|
||||
"description": "Detects when IME impersonates a user context for app installation.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-INSTALL-HANDLER",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ExecutionActionHandler\\] Handler invoked with execution type: Install for policy with id: {GUID}",
|
||||
"pattern": "^\\[Win32App\\]\\[ExecutionActionHandler\\] Handler invoked with execution type: Install for policy with id: {GUID}",
|
||||
"action": "updateStateInstalling",
|
||||
"description": "Detects when the IME execution handler starts installing an app.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-LAUNCH-INSTALLER",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Launch Win32AppInstaller in \\w+? session",
|
||||
"pattern": "^\\[Win32App\\] Launch Win32AppInstaller in \\w+? session",
|
||||
"action": "updateStateInstalling",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects when the Win32 app installer process is launched in a user or system session.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-NO-ACTION",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ActionProcessor\\] No action required for app with id: {GUID}",
|
||||
"pattern": "^\\[Win32App\\]\\[ActionProcessor\\] No action required for app with id: {GUID}",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when IME determines no action is required for an app (already in desired state).",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-NOT-APPLICABLE-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ApplicabilityActionHandler\\] Applicability check for policy with id: {GUID} resulted in action status: Success and applicability state: NotApplicable",
|
||||
"pattern": "^\\[Win32App\\]\\[ApplicabilityActionHandler\\] Applicability check for policy with id: {GUID} resulted in action status: Success and applicability state: NotApplicable",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's applicability check returns NotApplicable (requirements not met).",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-NOT-APPLICABLE-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Applicability state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}ApplicabilityState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}ScriptRequirementRuleNotMet\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Applicability state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}ApplicabilityState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}ScriptRequirementRuleNotMet\"{1,2}",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's applicability state is set to ScriptRequirementRuleNotMet (PowerShell requirement script returned false). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-NOT-APPLICABLE-3",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Desired state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DesiredState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}None\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Desired state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DesiredState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}None\"{1,2}",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's desired state is set to None (not targeted or not applicable). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-NOT-APPLICABLE-4",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ReportingManager\\] Desired state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DesiredState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}NotPresent\"{1,2}",
|
||||
"pattern": "^\\[Win32App\\]\\[ReportingManager\\] Desired state for app with id: {GUID} has been updated\\. Report delta: \\{\"{1,2}DesiredState\"{1,2}:\\{\"{1,2}OldValue\"{1,2}:null,\"{1,2}NewValue\"{1,2}:\"{1,2}NotPresent\"{1,2}",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's desired state is set to NotPresent (targeted for uninstall or not required). Quote-flexible since 2026-08-17: IME 1.104 serializes the report-delta JSON with single quotes ({\"Key\":...) where older versions logged doubled quotes ({\"\"Key\"\":...) - every literal quote in the delta is matched as \"{1,2} to cover both.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-NOT-TARGETED-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID}, targeted intent: RequiredInstall, and enforceability: Enforceable has projected enforcement classification: NotApplicableOrNotTargeted with desired state: None\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID}, targeted intent: RequiredInstall, and enforceability: Enforceable has projected enforcement classification: NotApplicableOrNotTargeted with desired state: None\\.",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app with RequiredInstall intent is classified as NotApplicableOrNotTargeted (new log format).",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-NOT-TARGETED-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID}, effective intent: RequiredInstall, and enforceability: Enforceable has projected enforcement classification: NotApplicableOrNotTargeted with desired state: None\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[ActionProcessor\\] App with id: {GUID}, effective intent: RequiredInstall, and enforceability: Enforceable has projected enforcement classification: NotApplicableOrNotTargeted with desired state: None\\.",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app with effective RequiredInstall intent is classified as NotApplicableOrNotTargeted.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-POLICIES",
|
||||
"category": "currentPhase",
|
||||
"pattern": "Get policies =\\s*(?<policies>.*)",
|
||||
"pattern": "^Get policies =\\s*(?<policies>.*)",
|
||||
"action": "policiesDiscovered",
|
||||
"description": "Captures the JSON policies list discovered by IME, used to build the app inventory.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-RESTART-STOP-PROCESSING",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[V3Processor\\] A restart is required to continue processing apps\\. Stop processing\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[V3Processor\\] A restart is required to continue processing apps\\. Stop processing\\.",
|
||||
"action": "updateStatePostponed",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects IME aborting app processing because an app requires a restart ('A restart is required to continue processing apps. Stop processing.', SideCarV3AppProcessor.cs). Postpones the current app: remaining apps do nothing until after the reboot, which previously looked like an unexplained stall mid-ESP.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SESSION-CHANGE",
|
||||
"category": "always",
|
||||
"pattern": "OnSessionChange:? (?<change>.*?)$",
|
||||
"pattern": "^OnSessionChange:? (?<change>.*?)$",
|
||||
"action": "imeSessionChange",
|
||||
"description": "Detects Windows session change events (logon, logoff, lock, unlock) reported by IME.",
|
||||
"enabled": true
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SET-CURRENT-1",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] ProcessAppWithDependencies starts for {GUID} with name",
|
||||
"action": "setCurrentApp",
|
||||
"description": "Detects when IME begins processing an app with its dependency chain, setting it as the current app. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SET-CURRENT-2",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] ExecManager: processing targeted app.*?\\bid\\W+{GUID}",
|
||||
"action": "setCurrentApp",
|
||||
"description": "Detects when the IME ExecManager starts processing a targeted app, setting it as current. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SET-CURRENT-3",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] ===Step=== Start to Present app {GUID}",
|
||||
"action": "setCurrentApp",
|
||||
"description": "Detects the start of app presentation step in IME, setting the app as current. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SET-CURRENT-4",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] SetCurrentDirectory:.*?\\\\{GUID}_\\d+",
|
||||
"pattern": "^\\[Win32App\\] SetCurrentDirectory:.*?\\\\{GUID}_\\d+",
|
||||
"action": "setCurrentApp",
|
||||
"description": "Detects when IME sets the working directory for an app's staging folder, setting it as current.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SHUTDOWN",
|
||||
"category": "currentPhase",
|
||||
"pattern": "EMS Agent received shutdown signal",
|
||||
"pattern": "^EMS Agent received shutdown signal",
|
||||
"action": "updateStatePostponed",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects when the IME agent receives a shutdown signal, postponing the current app installation.",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SKIP-APPLICABILITY-NOTMET",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] (?=.*?\\bapplicability\\b)(?=.*?NotMet\\b).*?\\bapp\\W+{GUID}\\b",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app's applicability check returns NotMet (old log format with lookaheads). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SKIP-DEPENDENCY-DETECT",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] app\\W+{GUID}\\b.*?\\bis a dependency\\b.*?\\bDetect Only mode\\b.*?\\bfinished processing",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when a dependency app in Detect Only mode finishes processing (no install needed). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SKIP-DETECTED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Detected app {GUID}.*?skip next step",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an already-detected app is skipped for further processing steps (old log format). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SKIP-FILTERS",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[V3Processor\\] All apps in the subgraph are not applicable due to assignment filters\\. Skipping processing\\.",
|
||||
"pattern": "^\\[Win32App\\]\\[V3Processor\\] All apps in the subgraph are not applicable due to assignment filters\\. Skipping processing\\.",
|
||||
"action": "updateStateSkipped",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects when all apps in a subgraph are skipped due to assignment filter exclusions.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SKIP-INSTALL-DETECTED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Detected app {GUID} and intent is to install, skip applicability check",
|
||||
"pattern": "^\\[Win32App\\] Detected app {GUID} and intent is to install, skip applicability check",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app targeted for install is already detected/installed, so the install is skipped (old log format).",
|
||||
"enabled": true
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SKIP-UNINSTALL-NOTDETECTED-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] NOT detected app {GUID}.*?skip un-installation",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app is not detected and uninstall is skipped (alternate old log format). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SKIP-UNINSTALL-NOTDETECTED",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Not detected app {GUID} and intent is to un-install, skip applicability check",
|
||||
"pattern": "^\\[Win32App\\] Not detected app {GUID} and intent is to un-install, skip applicability check",
|
||||
"action": "updateStateSkipped",
|
||||
"description": "Detects when an app targeted for uninstall is not detected, so the uninstall is skipped (old log format).",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-STARTED",
|
||||
"category": "always",
|
||||
"pattern": "EMS Agent Started",
|
||||
"pattern": "^EMS Agent Started",
|
||||
"action": "imeStarted",
|
||||
"description": "Detects when the Intune Management Extension (IME) agent starts up.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-STOP-SIGNAL",
|
||||
"category": "currentPhase",
|
||||
"pattern": "EMS Agent received stop signal",
|
||||
"pattern": "^EMS Agent received stop signal",
|
||||
"action": "updateStatePostponed",
|
||||
"parameters": { "useCurrentApp": "true" },
|
||||
"description": "Detects an IME service stop ('EMS Agent received stop signal', EmsAgentService.OnStop) — fired on manual restarts and agent-updater restarts, unlike the OS-shutdown-only 'shutdown signal' line matched by IME-SHUTDOWN. Postpones the current app installation, mirroring IME-SHUTDOWN semantics.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-SUBGRAPH",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\]\\[V3Processor\\] Processing subgraph with app ids: {GUID}",
|
||||
"pattern": "^\\[Win32App\\]\\[V3Processor\\] Processing subgraph with app ids: {GUID}",
|
||||
"action": "cancelStuckAndSetCurrent",
|
||||
"description": "Detects when V3Processor starts a new subgraph, cancelling any stuck app and setting the new app as current.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-TOKEN-FAILURE",
|
||||
"category": "always",
|
||||
"pattern": "Failed to get AAD token\\..*?errorCode = (?<errorCode>.*?)$",
|
||||
"pattern": "^Failed to get AAD token\\..*?errorCode = (?<errorCode>.*?)$",
|
||||
"action": "imeTokenFailure",
|
||||
"description": "IME token acquisition failure ('Failed to get AAD token. len = N using client id ... and resource id ..., errorCode = N', IntuneTokenManager, IntuneManagementExtension.log). This line appears ROUTINELY during the device phase (no user token exists yet), so the agent holds the failure pending: any IME-TOKEN-SUCCESS match clears it, and the ime_token_failure Warning fires only when no success follows for the whole 30-min grace window (then deduped to one Warning per distinct errorCode). A genuine auth outage (WAM broken, conditional access, network) runs long enough to hit the window. Verified against IME 1.97 and 1.104 decompiled sources.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-TOKEN-SUCCESS",
|
||||
"category": "always",
|
||||
"pattern": "Successfully get the token",
|
||||
"pattern": "^Successfully get the token",
|
||||
"action": "espPhaseDetected",
|
||||
"parameters": { "phase": "AccountSetup" },
|
||||
"description": "Detects successful token acquisition, indicating the AccountSetup ESP phase has started.",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-UPDATE-NAME-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] ExecManager: processing targeted app.*?name\\W+(?<name>.*?)\\W+id\\W+{GUID}",
|
||||
"action": "updateName",
|
||||
"description": "Captures the app name from ExecManager processing log (old format: name before id). DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-UPDATE-NAME-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] ProcessAppWithDependencies starts for {GUID} with name (?<name>.*?)$",
|
||||
"action": "updateName",
|
||||
"description": "Captures the app name from ProcessAppWithDependencies log entry. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-UPDATE-NAME-3",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Processing app \\(id={GUID}, name = (?<name>.*?)\\)",
|
||||
"action": "updateName",
|
||||
"description": "Captures the app name from the Processing app log entry. DISABLED 2026-08-17: emitter no longer exists in current IME (1.97.107, Dec 2025) - the old V1/V2 app processing pipeline was removed and this line is not logged anymore (verified against decompiled source, see tasks/ime-v1-code-audit-2026-08-17.md). Kept for history.",
|
||||
"enabled": false
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-USER-SESSION-COMPLETED",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] \\.+ Completed user session \\d+, userId: (?<userId>[a-f0-9\\-]+), userSID: (?<userSid>S-[\\d\\-]+) \\.+",
|
||||
"pattern": "^\\[Win32App\\] \\.+ Completed user session \\d+, userId: (?<userId>[a-f0-9\\-]+), userSID: (?<userSid>S-[\\d\\-]+) \\.+",
|
||||
"action": "enrollmentCompleted",
|
||||
"description": "Detects when IME completes a user session, indicating enrollment has finished for that user.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-USER-SESSION-ZERO-APPS",
|
||||
"category": "always",
|
||||
"pattern": "\\[Win32App\\] Get 0 apps for user session (?<sessionId>\\d+), user id = (?<userId>[a-f0-9\\-]+)",
|
||||
"pattern": "^\\[Win32App\\] Get 0 apps for user session (?<sessionId>\\d+), user id = (?<userId>[a-f0-9\\-]+)",
|
||||
"action": "userSessionZeroApps",
|
||||
"description": "Detects when IME enumerated a user session and found zero targeted Win32 apps. IME then skips its 'Completed user session' line entirely (the zero-app branch in ApplicationPoller continues before that log statement), so this line is the only evidence that user-phase enforcement finished on a device with no user-targeted apps. Treated as equivalent to a completed user session.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-WIN32-STATE-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] Got InstanceID: Win32App_{GUID}_\\d+, installationStateString: (?<state>\\d)",
|
||||
"pattern": "^\\[Win32App\\] Got InstanceID: Win32App_{GUID}_\\d+, installationStateString: (?<state>\\d)",
|
||||
"action": "updateWin32AppState",
|
||||
"description": "Captures the Win32 app installation state string from instance ID tracking (old log format).",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-WIN32-STATE-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[Win32App\\] (?:Got|Set) (?:ESP )?win32App state (?<state>\\w+) for {GUID}",
|
||||
"pattern": "^\\[Win32App\\] (?:Got|Set) (?:ESP )?win32App state (?<state>\\w+) for {GUID}",
|
||||
"action": "updateWin32AppState",
|
||||
"description": "Captures the Win32 app ESP state (e.g., Installed, InProgress) for a specific app.",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-WINGET-ERROR",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID}.*?- Operation Phase = Error",
|
||||
"pattern": "^\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID}.*?- Operation Phase = Error",
|
||||
"action": "updateStateError",
|
||||
"description": "Marks a WinGet (new Store) app as failed when its operation progress reports phase Error (WinGetOperationInstallPhase: Downloading/Installing/Queued/Finished/Error). Before this pattern only the Installing phase was matched, so WinGet failures never surfaced as app errors (IME 1.97 audit 2026-08-17).",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-WINGET-INSTALLING-1",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID} and user = .*?- Operation Phase = Installing",
|
||||
"pattern": "^\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID} and user = .*?- Operation Phase = Installing",
|
||||
"action": "updateStateInstalling",
|
||||
"description": "Detects WinGet app installation progress with Operation Phase = Installing (format variant 1).",
|
||||
"enabled": true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "../schema/ime-log-pattern.schema.json",
|
||||
"patternId": "IME-WINGET-INSTALLING-2",
|
||||
"category": "currentPhase",
|
||||
"pattern": "\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID}.*?- Operation Phase = Installing",
|
||||
"pattern": "^\\[WinGetMessageProcessor\\] Processing Progress for app = {GUID}.*?- Operation Phase = Installing",
|
||||
"action": "updateStateInstalling",
|
||||
"description": "Detects WinGet app installation progress with Operation Phase = Installing (format variant 2).",
|
||||
"enabled": true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user