mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
fix ANALYZE-APP-010: correlate app regression by appId using new source type
The previous rule incorrectly matched any app_install_completed event against any app_install_failed event without correlating by appId. Changes: - RuleEngine: add app_state_regression source that finds apps where app_install_completed is followed by app_install_failed for the same appId, with optional DataField filter on the failed event; returns appId, appName, both event details and errorDetail as evidence - AnalyzeRule: document new app_state_regression source in comment - ANALYZE-APP-010: rewrite to use single app_state_regression condition (IME-ERROR-REPORT filter) + enrollment_complete check; evidence now contains the exact app name and id that regressed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -568,51 +568,66 @@ namespace AutopilotMonitor.Functions.Services
|
||||
|
||||
/// <summary>
|
||||
/// Detects when an app was initially reported as successfully installed but then
|
||||
/// transitioned back to an error state (IME-ERROR-REPORT), while enrollment completed successfully.
|
||||
/// transitioned back to an error state (IME-ERROR-REPORT), correlated by appId.
|
||||
/// Uses the app_state_regression source which matches the exact same app across both events.
|
||||
/// This is a silent failure that can leave apps broken post-enrollment.
|
||||
/// </summary>
|
||||
private static AnalyzeRule CreateAppPostSuccessFailureRule() => new AnalyzeRule
|
||||
{
|
||||
RuleId = "ANALYZE-APP-010",
|
||||
Title = "App Reverted to Error After Successful Install",
|
||||
Description = "Detects when an app was marked as successfully installed but subsequently reported an error state, even though enrollment completed successfully. The app may be broken post-enrollment.",
|
||||
Description = "Detects when a specific app (matched by appId) was first reported as successfully installed but subsequently reported an IME enforcement error — while enrollment completed successfully. The app may be broken post-enrollment.",
|
||||
Severity = "warning",
|
||||
Category = "apps",
|
||||
Trigger = "correlation",
|
||||
BaseConfidence = 65,
|
||||
BaseConfidence = 75,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new RuleCondition { Signal = "app_initially_succeeded", Source = "event_type", EventType = "app_install_completed", Operator = "exists", Value = "", Required = true },
|
||||
new RuleCondition { Signal = "app_post_success_failure", Source = "event_type", EventType = "app_install_failed", DataField = "errorPatternId", Operator = "equals", Value = "IME-ERROR-REPORT", Required = true },
|
||||
new RuleCondition { Signal = "enrollment_succeeded", Source = "event_type", EventType = "enrollment_complete", Operator = "exists", Value = "", Required = true }
|
||||
// Single condition: finds apps where completed -> failed regression exists for the same appId,
|
||||
// and the failed event has errorPatternId = IME-ERROR-REPORT
|
||||
new RuleCondition
|
||||
{
|
||||
Signal = "app_regression",
|
||||
Source = "app_state_regression",
|
||||
DataField = "errorPatternId",
|
||||
Operator = "equals",
|
||||
Value = "IME-ERROR-REPORT",
|
||||
Required = true
|
||||
},
|
||||
new RuleCondition
|
||||
{
|
||||
Signal = "enrollment_succeeded",
|
||||
Source = "event_type",
|
||||
EventType = "enrollment_complete",
|
||||
Operator = "exists",
|
||||
Value = "",
|
||||
Required = true
|
||||
}
|
||||
},
|
||||
ConfidenceFactors = new List<ConfidenceFactor>
|
||||
{
|
||||
new ConfidenceFactor { Signal = "enforcement_state_changed", Condition = "exists", Weight = 20 },
|
||||
new ConfidenceFactor { Signal = "error_code_present", Condition = "exists", Weight = 10 }
|
||||
},
|
||||
ConfidenceThreshold = 60,
|
||||
Explanation = @"An app was initially reported as **successfully installed** (`app_install_completed`) but then transitioned back to an **Error** state via an `IME-ERROR-REPORT` event — while the overall enrollment completed successfully.
|
||||
ConfidenceThreshold = 70,
|
||||
Explanation = @"An app was initially reported as **successfully installed** (`app_install_completed`) but the IME (Intune Management Extension) subsequently reported an **Error** state for the same app via an `IME-ERROR-REPORT` event — while the overall enrollment completed successfully.
|
||||
|
||||
This pattern typically occurs when:
|
||||
- The IME (Intune Management Extension) re-evaluates enforcement state after the initial install report and detects a failure (e.g., `EnforcementState` changed from `Success` → `Error`)
|
||||
- A post-install detection check fails, causing the enforcement error code to be updated (e.g., error `-2016345060`)
|
||||
- The app may appear installed in the ESP but is actually in a broken or partially-deployed state on the device
|
||||
This pattern occurs when the IME re-evaluates enforcement state after the initial install report and detects a failure, e.g. `EnforcementState` transitions from `Success` → `Error`. The affected app and its details are captured in the matched evidence.
|
||||
|
||||
Common causes:
|
||||
- A post-install detection check fails (detection rule mismatch)
|
||||
- The enforcement error code `-2016345060` (0x87D13B9C) indicates the app state diverged from expected
|
||||
- The app may appear installed in the ESP summary but is in an error state on the device
|
||||
|
||||
**The enrollment succeeded, but the affected app requires attention.**",
|
||||
Remediation = new List<RemediationStep>
|
||||
{
|
||||
new RemediationStep
|
||||
{
|
||||
Title = "Investigate the app's post-install state",
|
||||
Title = "Investigate the app's post-install enforcement state",
|
||||
Steps = new List<string>
|
||||
{
|
||||
"Check the Intune portal: Device > Apps — look for the app and confirm its current enforcement state",
|
||||
"Note the appId and appName from the rule finding — these identify the exact affected app",
|
||||
"Check the Intune portal: Devices > [Device] > Apps — confirm the current enforcement state of the app",
|
||||
"Review IME logs on the device: C:\\ProgramData\\Microsoft\\IntuneManagementExtension\\Logs\\IntuneManagementExtension.log",
|
||||
"Search the log for the appId or appName and look for 'EnforcementState' transitions near the enrollment time",
|
||||
"Check the EnforcementErrorCode — error -2016345060 (0x87D13B9C) typically indicates a detection failure after install",
|
||||
"Verify the app's detection rule in Intune is correct and matches the installed state",
|
||||
"Trigger a manual sync on the device (Company Portal or Settings > Accounts > Access work or school > Sync) to force re-evaluation"
|
||||
"Search the log for the appId and look for 'EnforcementState' transitions (Success → Error) near the enrollment time",
|
||||
"Error -2016345060 (0x87D13B9C) typically indicates a detection failure after install — verify the app's detection rule in Intune",
|
||||
"Trigger a manual sync (Company Portal or Settings > Accounts > Access work or school > Sync) to force re-evaluation"
|
||||
}
|
||||
},
|
||||
new RemediationStep
|
||||
@@ -620,7 +635,7 @@ This pattern typically occurs when:
|
||||
Title = "Fix detection or installation issues",
|
||||
Steps = new List<string>
|
||||
{
|
||||
"If detection fails: correct the detection rule (file path, registry key, or script) in the Intune app configuration",
|
||||
"If detection fails: correct the detection rule (file path, registry key, or PowerShell script) in the Intune app configuration",
|
||||
"If the app itself is broken: redeploy or reassign the app to trigger a fresh install",
|
||||
"Consider adding a post-install validation script to catch this scenario proactively"
|
||||
}
|
||||
|
||||
@@ -161,6 +161,9 @@ namespace AutopilotMonitor.Functions.Services
|
||||
case "app_install_duration":
|
||||
return EvaluateAppInstallDurationCondition(condition, events);
|
||||
|
||||
case "app_state_regression":
|
||||
return EvaluateAppStateRegressionCondition(condition, events);
|
||||
|
||||
default:
|
||||
return (false, "unknown source");
|
||||
}
|
||||
@@ -512,5 +515,71 @@ namespace AutopilotMonitor.Functions.Services
|
||||
|
||||
return (false, "no app install duration matched");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects apps that regressed: first seen as app_install_completed, then as app_install_failed,
|
||||
/// correlated by appId. Optionally filters the failed event by a DataField/Operator/Value condition.
|
||||
/// Returns evidence with appId, appName, and both event details.
|
||||
/// </summary>
|
||||
private (bool matched, object evidence) EvaluateAppStateRegressionCondition(RuleCondition condition, List<EnrollmentEvent> events)
|
||||
{
|
||||
var sortedEvents = events.OrderBy(e => e.Timestamp).ThenBy(e => e.Sequence).ToList();
|
||||
|
||||
var completedEvents = sortedEvents
|
||||
.Where(e => string.Equals(e.EventType, "app_install_completed", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
var failedEvents = sortedEvents
|
||||
.Where(e => string.Equals(e.EventType, "app_install_failed", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
if (!completedEvents.Any() || !failedEvents.Any())
|
||||
return (false, "no completed/failed event pair found");
|
||||
|
||||
foreach (var failedEvent in failedEvents)
|
||||
{
|
||||
// Apply optional DataField filter on the failed event
|
||||
if (!string.IsNullOrEmpty(condition.DataField) && !string.IsNullOrEmpty(condition.Operator))
|
||||
{
|
||||
var fieldValue = GetDataFieldValue(failedEvent, condition.DataField);
|
||||
if (fieldValue == null || !MatchesOperator(fieldValue, condition.Operator, condition.Value ?? ""))
|
||||
continue;
|
||||
}
|
||||
|
||||
var failedAppId = GetDataFieldValue(failedEvent, "appId");
|
||||
var failedAppName = GetDataFieldValue(failedEvent, "appName");
|
||||
var appKey = !string.IsNullOrWhiteSpace(failedAppId) ? failedAppId : failedAppName;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(appKey))
|
||||
continue;
|
||||
|
||||
// Find a completed event for the same app that occurred before the failed event
|
||||
var matchingCompleted = completedEvents.LastOrDefault(c =>
|
||||
c.Timestamp <= failedEvent.Timestamp &&
|
||||
string.Equals(
|
||||
GetDataFieldValue(c, "appId") ?? GetDataFieldValue(c, "appName"),
|
||||
appKey,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (matchingCompleted == null)
|
||||
continue;
|
||||
|
||||
return (true, new Dictionary<string, object>
|
||||
{
|
||||
["appId"] = failedAppId ?? string.Empty,
|
||||
["appName"] = failedAppName ?? appKey,
|
||||
["completedEventId"] = matchingCompleted.EventId ?? string.Empty,
|
||||
["completedSequence"] = matchingCompleted.Sequence,
|
||||
["completedTimestamp"] = matchingCompleted.Timestamp,
|
||||
["failedEventId"] = failedEvent.EventId ?? string.Empty,
|
||||
["failedSequence"] = failedEvent.Sequence,
|
||||
["failedTimestamp"] = failedEvent.Timestamp,
|
||||
["errorPatternId"] = GetDataFieldValue(failedEvent, "errorPatternId") ?? string.Empty,
|
||||
["errorDetail"] = GetDataFieldValue(failedEvent, "errorDetail") ?? failedEvent.Message ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
return (false, "no app found with completed-then-failed regression");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace AutopilotMonitor.Shared.Models
|
||||
public string Signal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Source of the signal: "event_type", "event_data", "phase_duration", "event_count", "app_install_duration"
|
||||
/// Source of the signal: "event_type", "event_data", "phase_duration", "event_count", "app_install_duration", "app_state_regression"
|
||||
/// </summary>
|
||||
public string Source { get; set; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user