mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
fixed constants
fix for analyze rule reboot loop added completed timestamp to progress portal longer os age default in bootstrapper removed legacy filed name in events
This commit is contained in:
@@ -31,17 +31,19 @@ param(
|
||||
[string]$AgentDownloadUrl = "https://autopilotmonitor.blob.core.windows.net/agent/AutopilotMonitor-Agent.zip",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[int]$MaxOsAgeMinutes = 60
|
||||
[int]$MaxOsAgeMinutes = 120
|
||||
)
|
||||
|
||||
# Configuration - Everything in ProgramData for easy cleanup
|
||||
$AgentBasePath = "$env:ProgramData\AutopilotMonitor"
|
||||
$AgentBinPath = "$AgentBasePath\Agent"
|
||||
$LogFile = "$AgentBasePath\Bootstrap.log"
|
||||
$AgentLogPath = "$AgentBasePath\Logs"
|
||||
$LogFile = "$AgentLogPath\Bootstrap.log"
|
||||
|
||||
# Ensure directories exist
|
||||
New-Item -Path $AgentBasePath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
New-Item -Path $AgentBinPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
New-Item -Path $AgentLogPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message)
|
||||
|
||||
@@ -347,7 +347,6 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
{ "appId", Id },
|
||||
{ "name", Name ?? Id },
|
||||
{ "appName", Name ?? Id },
|
||||
{ "state", InstallationState.ToString() },
|
||||
{ "intent", Intent.ToString() },
|
||||
|
||||
@@ -1 +1 @@
|
||||
30
|
||||
31
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
CleanupOnExit = false,
|
||||
SelfDestructOnComplete = false,
|
||||
KeepLogFile = true,
|
||||
ImeMatchLogPath = @"C:\ProgramData\AutopilotMonitor\Logs\ime_pattern_matches.log",
|
||||
|
||||
Collectors = collectors,
|
||||
GatherRules = gatherRules,
|
||||
ImeLogPatterns = imeLogPatterns,
|
||||
|
||||
@@ -409,10 +409,7 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
|
||||
if (!isRelevant) return;
|
||||
|
||||
// Agent sends "appName" (and also "name") in the Data dictionary
|
||||
var appName = evt.Data?.ContainsKey("appName") == true ? evt.Data["appName"]?.ToString() : null;
|
||||
if (string.IsNullOrEmpty(appName))
|
||||
appName = evt.Data?.ContainsKey("name") == true ? evt.Data["name"]?.ToString() : null;
|
||||
if (string.IsNullOrEmpty(appName)) return;
|
||||
|
||||
if (!summaries.TryGetValue(appName, out var summary))
|
||||
|
||||
@@ -181,18 +181,18 @@ namespace AutopilotMonitor.Functions.Services
|
||||
{
|
||||
RuleId = "ANALYZE-APP-006",
|
||||
Title = "Reboot Required Loop",
|
||||
Description = "Detects when an app installation enters a reboot-required loop.",
|
||||
Description = "Detects when the same app installation is attempted 3 or more times, indicating a reboot-required loop.",
|
||||
Severity = "high",
|
||||
Category = "apps",
|
||||
BaseConfidence = 70,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new RuleCondition { Signal = "reboot_loop", Source = "event_count", EventType = "app_install_started", Operator = "count_gte", Value = "3", Required = true }
|
||||
new RuleCondition { Signal = "reboot_loop", Source = "event_count", EventType = "app_install_started", DataField = "appId", Operator = "count_per_group_gte", Value = "3", Required = true }
|
||||
},
|
||||
Explanation = "An app appears to be in a reboot loop - it has been attempted multiple times. This may indicate that the app requires a reboot that is not being honored by the ESP.",
|
||||
Explanation = "The same app has been installed 3 or more times during this enrollment. This typically indicates the app requires a reboot between install attempts, but the ESP is not honoring the reboot request, causing an install-detect-retry loop.",
|
||||
Remediation = new List<RemediationStep>
|
||||
{
|
||||
new RemediationStep { Title = "Fix reboot handling", Steps = new List<string> { "Check if the app's return code mapping includes reboot codes", "Verify ESP reboot behavior settings", "Consider setting the app to 'Hard reboot' return code" } }
|
||||
new RemediationStep { Title = "Fix reboot handling", Steps = new List<string> { "Check if the app's return code mapping includes reboot codes (3010, 1641)", "Verify ESP reboot behavior settings", "Consider setting the app to 'Hard reboot' return code in Intune" } }
|
||||
},
|
||||
Tags = new[] { "apps", "reboot", "loop" }
|
||||
};
|
||||
|
||||
@@ -233,8 +233,39 @@ namespace AutopilotMonitor.Functions.Services
|
||||
private (bool matched, object evidence) EvaluateEventCountCondition(RuleCondition condition, List<EnrollmentEvent> events)
|
||||
{
|
||||
var matchingEvents = events.Where(e => MatchesEventType(e, condition.EventType)).ToList();
|
||||
var count = matchingEvents.Count;
|
||||
|
||||
// count_per_group_gte: group by DataField value (e.g. appId), fire if any group >= threshold
|
||||
if (condition.Operator == "count_per_group_gte" && !string.IsNullOrEmpty(condition.DataField) && int.TryParse(condition.Value, out var groupThreshold))
|
||||
{
|
||||
var groups = matchingEvents
|
||||
.Select(e => new { Event = e, Key = GetDataFieldValue(e, condition.DataField) })
|
||||
.Where(x => !string.IsNullOrEmpty(x.Key))
|
||||
.GroupBy(x => x.Key, StringComparer.OrdinalIgnoreCase)
|
||||
.Where(g => g.Count() >= groupThreshold)
|
||||
.ToList();
|
||||
|
||||
if (groups.Any())
|
||||
{
|
||||
var worst = groups.OrderByDescending(g => g.Count()).First();
|
||||
var firstEvent = worst.First().Event;
|
||||
var appName = GetDataFieldValue(firstEvent, "appName") ?? worst.Key;
|
||||
return (true, new Dictionary<string, object>
|
||||
{
|
||||
["eventId"] = firstEvent.EventId,
|
||||
["sequence"] = firstEvent.Sequence,
|
||||
["timestamp"] = firstEvent.Timestamp,
|
||||
["groupKey"] = worst.Key,
|
||||
["appName"] = appName,
|
||||
["count"] = worst.Count(),
|
||||
["threshold"] = groupThreshold
|
||||
});
|
||||
}
|
||||
|
||||
return (false, new Dictionary<string, object> { ["totalEvents"] = matchingEvents.Count, ["distinctApps"] = matchingEvents.Select(e => GetDataFieldValue(e, condition.DataField)).Where(k => k != null).Distinct(StringComparer.OrdinalIgnoreCase).Count() });
|
||||
}
|
||||
|
||||
// count_gte: global count across all matching events
|
||||
var count = matchingEvents.Count;
|
||||
if (condition.Operator == "count_gte" && int.TryParse(condition.Value, out var threshold))
|
||||
{
|
||||
if (count >= threshold)
|
||||
@@ -437,7 +468,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
foreach (var completionEvent in completionEvents)
|
||||
{
|
||||
var appId = GetDataFieldValue(completionEvent, "appId");
|
||||
var appName = GetDataFieldValue(completionEvent, "appName") ?? GetDataFieldValue(completionEvent, "name");
|
||||
var appName = GetDataFieldValue(completionEvent, "appName");
|
||||
var appKey = !string.IsNullOrWhiteSpace(appId) ? appId : appName;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(appKey))
|
||||
@@ -447,7 +478,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
(string.Equals(e.EventType, "app_install_started", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(e.EventType, "app_install_start", StringComparison.OrdinalIgnoreCase)) &&
|
||||
e.Timestamp <= completionEvent.Timestamp &&
|
||||
string.Equals(GetDataFieldValue(e, "appId") ?? GetDataFieldValue(e, "appName") ?? GetDataFieldValue(e, "name"), appKey, StringComparison.OrdinalIgnoreCase));
|
||||
string.Equals(GetDataFieldValue(e, "appId") ?? GetDataFieldValue(e, "appName"), appKey, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (startEvent == null)
|
||||
continue;
|
||||
|
||||
@@ -29,6 +29,11 @@ namespace AutopilotMonitor.Shared
|
||||
/// </summary>
|
||||
public const string StateDirectory = @"%ProgramData%\AutopilotMonitor\State";
|
||||
|
||||
/// <summary>
|
||||
/// Default path for the IME pattern match log file (debugging/diagnostics)
|
||||
/// </summary>
|
||||
public const string ImeMatchLogPath = @"%ProgramData%\AutopilotMonitor\Logs\ime_pattern_matches.log";
|
||||
|
||||
/// <summary>
|
||||
/// Scheduled Task name used to run the agent as SYSTEM
|
||||
/// </summary>
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace AutopilotMonitor.Shared.Models
|
||||
/// <summary>
|
||||
/// Optional path to IME pattern match log file.
|
||||
/// </summary>
|
||||
public string ImeMatchLogPath { get; set; } = @"C:\ProgramData\AutopilotMonitor\Logs\ime_pattern_matches.log";
|
||||
public string ImeMatchLogPath { get; set; } = Constants.ImeMatchLogPath;
|
||||
|
||||
public CollectorConfiguration Collectors { get; set; }
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function LandingPage() {
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-8 leading-relaxed">
|
||||
Real-time insights, intelligent troubleshooting, and comprehensive analytics for your Autopilot deployments.
|
||||
Monitor every phase, track performance, and resolve issues faster than ever before.
|
||||
Monitor every phase, run customizable analyze rules, and resolve issues faster than ever before.
|
||||
</p>
|
||||
<div className="flex items-center justify-center space-x-4">
|
||||
<button
|
||||
@@ -134,7 +134,7 @@ export default function LandingPage() {
|
||||
Everything you need to monitor Autopilot
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{/* Feature 1 */}
|
||||
{/* Feature 1 - Real-Time Monitoring */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -142,51 +142,81 @@ export default function LandingPage() {
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Real-Time Monitoring</h3>
|
||||
<p className="text-gray-600">
|
||||
Watch Autopilot deployments in real-time with live event streaming. Track every phase from device registration to user login.
|
||||
<p className="text-gray-600 mb-4">
|
||||
Watch Autopilot deployments in near real-time with live event streaming. Track every phase from device registration to user login.
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{["Live phase tracking", "Near realtime push updates", "Per-device event stream"].map(item => (
|
||||
<li key={item} className="flex items-center text-sm text-gray-500">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-blue-400 mr-2 shrink-0" />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Feature 2 */}
|
||||
{/* Feature 2 - Rich Analytics */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Usage Analytics</h3>
|
||||
<p className="text-gray-600">
|
||||
Comprehensive metrics on deployment success rates, performance trends, and hardware insights across your tenant.
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Rich Analytics</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Comprehensive metrics on deployment success rates, performance trends, and hardware insights — powered by customizable analyze rules.
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{["Customizable analyze rules", "Success & failure rates", "Hardware model insights"].map(item => (
|
||||
<li key={item} className="flex items-center text-sm text-gray-500">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-indigo-400 mr-2 shrink-0" />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Feature 3 */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow">
|
||||
{/* Feature 3 - Multi-Tenant / MSP (Planned) */}
|
||||
<div className="relative bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<span className="absolute top-4 right-4 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-amber-100 text-amber-700 border border-amber-200">
|
||||
Planned
|
||||
</span>
|
||||
<div className="w-12 h-12 bg-purple-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Multi-Tenant Ready</h3>
|
||||
<p className="text-gray-600">
|
||||
Built for MSPs and enterprises. Manage multiple tenants from a single dashboard with tenant isolation and role-based access.
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">MSP Support</h3>
|
||||
<p className="text-gray-600 mb-4">
|
||||
Built for MSPs and enterprises. Manage multiple customer tenants from a single dashboard with full tenant isolation and role-based access.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Feature 4 */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow">
|
||||
{/* Feature 4 - Intelligent Alerts (Planned) */}
|
||||
<div className="relative bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<span className="absolute top-4 right-4 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-amber-100 text-amber-700 border border-amber-200">
|
||||
Planned
|
||||
</span>
|
||||
<div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Intelligent Alerts</h3>
|
||||
<p className="text-gray-600">
|
||||
<p className="text-gray-600 mb-4">
|
||||
Get notified instantly when deployments fail or encounter issues. Smart alerts help you catch problems before users do.
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{["Webhook notifications", "Teams & Slack integration", "Configurable rule results"].map(item => (
|
||||
<li key={item} className="flex items-center text-sm text-gray-500">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-400 mr-2 shrink-0" />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Feature 5 */}
|
||||
{/* Feature 5 - Event Timeline */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<div className="w-12 h-12 bg-orange-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-orange-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -194,12 +224,20 @@ export default function LandingPage() {
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Event Timeline</h3>
|
||||
<p className="text-gray-600">
|
||||
Detailed event timeline for every deployment session. Drill down into logs, errors, and warnings to troubleshoot efficiently.
|
||||
<p className="text-gray-600 mb-4">
|
||||
Detailed event timeline for every deployment session. Drill down into events, errors, and warnings to troubleshoot efficiently.
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{["Phase-by-phase breakdown", "App install details", "Error & warning highlights"].map(item => (
|
||||
<li key={item} className="flex items-center text-sm text-gray-500">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-orange-400 mr-2 shrink-0" />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Feature 6 */}
|
||||
{/* Feature 6 - Audit Logging */}
|
||||
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow">
|
||||
<div className="w-12 h-12 bg-red-100 rounded-xl flex items-center justify-center mb-4">
|
||||
<svg className="w-6 h-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -207,9 +245,17 @@ export default function LandingPage() {
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-3">Audit Logging</h3>
|
||||
<p className="text-gray-600">
|
||||
<p className="text-gray-600 mb-4">
|
||||
Complete audit trail of all actions and changes. Meet compliance requirements with detailed logging and data retention policies.
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{["Admin action history", "Configurable retention", "Tamper-evident records"].map(item => (
|
||||
<li key={item} className="flex items-center text-sm text-gray-500">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-red-400 mr-2 shrink-0" />
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -236,7 +282,7 @@ export default function LandingPage() {
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-gray-200 bg-white/50 backdrop-blur-sm py-8 px-6">
|
||||
<div className="max-w-7xl mx-auto text-center text-gray-600">
|
||||
<p>© 2026 Autopilot Monitor. Powered by Azure and Microsoft Identity.</p>
|
||||
<p>© 2026 Autopilot Monitor developed by Oliver Kieselbach and powered by Azure and Microsoft Identity.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -748,6 +748,15 @@ export default function ProgressPortalPage() {
|
||||
Your device is ready to use! Total setup time:{" "}
|
||||
{Math.round(session.durationSeconds / 60)} minutes.
|
||||
</p>
|
||||
<p className="text-xs text-green-600 mt-1">
|
||||
Completed at{" "}
|
||||
{new Date(
|
||||
new Date(session.startedAt).getTime() + session.durationSeconds * 1000
|
||||
).toLocaleString(undefined, {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user