mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
35 KiB
35 KiB
Autopilot Monitor – Architecture Guide
Living document describing the system architecture, design decisions, and conventions.
High-Level Overview
+----------------------------------------------------------------------------------+
| Autopilot Monitor - Architecture |
+----------------------------------------------------------------------------------+
[Admin/User Browser]
|
| HTTPS
v
+-----------------------------+ OIDC +-------------------------------+
| Web App (Next.js) |<---------------->| Microsoft Identity (Entra ID) |
| Dashboard / Progress / Docs | | Auth / Authorization |
+-----------------------------+ +-------------------------------+
^ ^
| |
| | Live updates
| |
| | +---------------------------+
| +-------------------------| SignalR Hub |
| | Real-time event stream |
| +---------------------------+
| ^
| REST / API |
v | events
+---------------------------+ Data & |
| Azure Functions Backend |------> Integrations ---+
| APIs / Ingestion / Rules | \
+---------------------------+ \ +---------------------------+
^ +----->| Storage Tables |
| mTLS | Sessions / Events / Rules |
| telemetry / events +---------------------------+
| +---------------------------+
+---------------------------+ | Notifications |
| Agent on Windows Devices | | Teams / Webhooks ... |
| Enrollment + app progress | +---------------------------+
+---------------------------+
Core Data Flow:
Device (Agent) ──NDJSON+gzip──► Azure Functions ──► Azure Table Storage
│ │
├── SignalR ─────────►│
│ │
Web Dashboard ◄── REST API ◄──┘
| Component | Technology | Purpose |
|---|---|---|
| Agent | .NET Framework 4.8 (Windows) | Runs on devices during enrollment, collects telemetry |
| SummaryDialog | .NET Framework 4.8 (WPF) | Post-enrollment summary UI (outcome, app timeline, auto-close) |
| Backend | Azure Functions .NET 8 (Isolated Worker) | REST API, event processing, rule engine, storage |
| Web | Next.js 15 + TypeScript + React 18 | Dashboard, settings, analytics, real-time UI |
| Shared | .NET Standard 2.0 | DTOs, models, enums, constants shared between Agent & Backend |
Table of Contents
- Solution Structure
- Agent
- Backend
- Web Frontend
- Shared Library
- Data Model
- Security Architecture
- Real-Time Communication
- Rules Engine
- Session Lifecycle
- Configuration Hierarchy
- Diagnostics & Upload
- Infrastructure & Deployment
- Build & Development
Solution Structure
AutopilotMonitor.sln
├── src/
│ ├── Shared/AutopilotMonitor.Shared/ (netstandard2.0)
│ ├── Agent/
│ │ ├── AutopilotMonitor.Agent/ (net48, Exe – entry point)
│ │ ├── AutopilotMonitor.Agent.Core/ (net48, Lib – core logic)
│ │ └── AutopilotMonitor.SummaryDialog/ (net48, WPF – enrollment summary UI)
│ ├── Backend/
│ │ ├── AutopilotMonitor.Functions/ (net8.0, Azure Functions v4)
│ │ └── AutopilotMonitor.Functions.Tests/ (net8.0, xUnit)
│ └── Web/
│ └── autopilot-monitor-web/ (Next.js 15, TypeScript)
├── rules/
│ ├── gather/ Individual gather rule JSONs
│ ├── analyze/ Individual analyze rule JSONs
│ ├── ime-log-patterns/ IME regex pattern JSONs
│ ├── schema/ JSON Schema definitions
│ ├── scripts/ combine.js
│ └── dist/ Combined output (embedded in Functions)
└── .github/workflows/ CI/CD pipelines
Project References:
Shared ◄── Agent.Core ◄── Agent
◄── SummaryDialog
Shared ◄── Functions ◄── Functions.Tests
Web (independent – communicates via REST + SignalR)
Agent
Entry Point & Modes
File: src/Agent/AutopilotMonitor.Agent/Program.cs
Three execution modes:
- Normal mode (default) – Main enrollment monitoring loop
--installmode – Deploys agent via Scheduled Task (Intune package)--run-gather-rulesmode – One-shot offline data collection, then exits
Startup Sequence (Normal Mode)
- Single-instance check (prevent duplicate agent processes)
FetchRemoteConfig()– Backend config with 15s timeout, disk cache fallbackRegisterSessionAsync()– Register session (5 retries, exponential backoff)StartWatching()– Enable FileSystemWatcher for event spoolStartEventCollectors()– EspAndHelloTracker (always on)StartOptionalCollectors()– PerformanceCollector, AgentSelfMetrics, EnrollmentTracker, DesktopArrivalDetectorStartGatherRuleExecutor()– Backend-defined data collection rulesInitializeAnalyzers()+RunStartupAnalyzers()– Security baseline
Key Services
| Service | Location | Responsibility |
|---|---|---|
MonitoringService |
Core/Monitoring/Core/ |
Main orchestrator: starts/stops collectors, manages upload loop |
BackendApiClient |
Core/Monitoring/Network/ |
HTTP client with mTLS cert + hardware headers |
EventSpool |
Core/Monitoring/Core/ |
Offline event storage (JSON files), FileSystemWatcher-based |
EnrollmentTracker |
Core/Monitoring/Tracking/ |
Central enrollment state machine, 3 completion paths |
ImeLogTracker |
Core/Monitoring/Tracking/ |
Parses IME logs with backend-provided regex patterns |
EspAndHelloTracker |
Core/Monitoring/Collectors/ |
Windows event log monitoring (ESP exit, Hello provisioning) |
DesktopArrivalDetector |
Core/Monitoring/Collectors/ |
Polls for explorer.exe under real user (non-SYSTEM) |
PerformanceCollector |
Core/Monitoring/Collectors/ |
CPU/Memory/Disk metrics |
GatherRuleExecutor |
Core/Monitoring/Collectors/ |
Executes backend-defined data collection rules |
DiagnosticsPackageService |
Core/Monitoring/Core/ |
Creates ZIP + uploads via short-lived SAS URL |
RemoteConfigService |
Core/Configuration/ |
Fetches & caches backend config with disk fallback |
SessionPersistence |
Core/Monitoring/Core/ |
Persists session ID, sequence counter, markers |
Directory Layout (Agent.Core)
Monitoring/
├── Analyzers/ Security checks (LocalAdminAnalyzer)
├── Collectors/ Data collectors (ESP, Hello, Performance, GatherRules, Diagnostics)
├── Core/ Orchestration (MonitoringService, EventSpool, SessionPersistence)
├── Interop/ P/Invoke declarations (process creation, registry change notifications)
├── Network/ API client, emergency reporter, geo-location, network metrics
├── Replay/ Log replay for testing/simulation
└── Tracking/ Enrollment state machine, IME parser, script tracking
Event Collection & Upload
- Collector emits
EnrollmentEvent→ sequence number auto-assigned (thread-safe Interlocked) - Event saved to spool as JSON file:
event_{timestamp}_{sequence}.json - FileSystemWatcher triggers debounce timer (configurable
UploadIntervalSeconds, default 30s) - Batch upload: NDJSON + gzip compression, max 100 events per batch
- Response handling:
DeviceKillSignal→ self-destruct;DeviceBlocked→ stop uploads
Idle Timeout & Lifetime
- Collector Idle Timeout: Default 15min (
CollectorIdleTimeoutMinutes). Tracks_lastRealEventTime. "Real" events = everything exceptperformance_snapshot,agent_metrics_snapshot, and*_stoppedvariants. Idle collectors auto-restart on new activity. - Agent Max Lifetime: Default 360min/6h (
AgentMaxLifetimeMinutes). Emitsenrollment_failedwithfailureType="agent_timeout". - Session Age Emergency Break: 48h absolute max. Checked at startup, triggers cleanup.
Agent Data Paths
%ProgramData%\AutopilotMonitor\
├── session.id, session.seq, session.created
├── whiteglove.complete
├── bootstrap-config.json
├── Logs/agent.log
├── Spool/event_*.json
├── Config/remote-config.json
└── State/
├── enrollment-state.json
├── ime-tracker-state.json
└── enrollment-complete.marker
Backend
Azure Functions Setup
- Runtime: .NET 8 Isolated Worker, Azure Functions v4
- Route Prefix:
/api - Monitoring: Application Insights with sampling
- Entry Point:
src/Backend/AutopilotMonitor.Functions/Program.cs
Endpoints (~61 Functions)
Agent-to-Cloud (device auth via cert/bootstrap):
| Route | Method | Purpose |
|---|---|---|
/agent/register-session |
POST | Register new enrollment session |
/agent/ingest |
POST | Upload events (NDJSON+gzip) |
/agent/config |
GET | Fetch agent configuration |
/agent/upload-url |
POST | Get short-lived SAS URL for diagnostics |
/agent/error |
POST | Report agent errors |
Version Management:
| Route | Method | Purpose |
|---|---|---|
/versions/block |
POST | Add version block/kill rule |
/versions/blocked |
GET | List active version block rules |
Bootstrap (pre-MDM auth):
| Route | Method | Purpose |
|---|---|---|
/bootstrap/sessions |
POST/GET | Create/list bootstrap sessions |
/bootstrap/sessions/{code} |
DELETE | Revoke bootstrap session |
/bootstrap/validate/{code} |
POST | Validate bootstrap code |
/bootstrap/register-session |
POST | Register via bootstrap token |
/bootstrap/ingest |
POST | Ingest via bootstrap token |
/bootstrap/config |
GET | Config via bootstrap token |
Web Portal (JWT auth):
| Category | Key Routes |
|---|---|
| Sessions | GET /sessions, GET /sessions/{id}, GET /sessions/{id}/events, POST /sessions/{id}/analysis, DELETE /sessions/{id}, POST /sessions/{id}/mark-failed |
| Rules | CRUD for /rules/gather, /rules/analyze, /rules/ime-log-patterns, POST /rules/reseed-from-github |
| Config | GET/POST /global/config, GET/PUT /config/{tenantId} |
| Auth | GET /auth/me, GET /auth/is-galactic-admin, CRUD /auth/galactic-admins |
| Tenants | CRUD /tenants/{id}/admins, POST /tenants/{id}/offboard |
| Devices | POST /devices/block, DELETE /devices/block/{serial}, GET /devices/blocked |
| Reports | POST /sessions/{id}/report, GET /galactic/session-reports |
| Metrics | GET /metrics/usage, GET /metrics/app, GET /metrics/geographic, GET /stats/platform |
| Galactic | /galactic/metrics/*, /galactic/audit/logs, /galactic/session-reports, /galactic/notifications (CRUD + dismiss) |
| Progress | GET /progress/sessions, GET /progress/sessions/{id}/events |
| SignalR | POST /realtime/negotiate, POST /realtime/groups/join, POST /realtime/groups/leave |
| Health | GET /health, GET /health/detailed |
Timer Trigger:
DailyMaintenanceFunction– Every 2 hours (0 0 */2 * * *): stale session detection, metrics aggregation, data cleanup
Key Backend Services
| Service | Responsibility |
|---|---|
TableStorageService |
Core data access for all 21 Azure Tables (split across 5 files) |
TenantConfigurationService |
Per-tenant config with 5-min cache |
AdminConfigurationService |
Global config with 5-min cache, syncs rate limits to tenants |
RateLimitService |
In-memory sliding window rate limiting (1-min window) |
SecurityValidator |
Centralized request validation (cert → rate limit → hardware → APV) |
RuleEngine |
Server-side analyze rule evaluation with confidence scoring |
MaintenanceService |
Cleanup, metrics aggregation, stale session detection |
BootstrapSessionService |
Bootstrap token lifecycle (create, validate, revoke) |
BlockedDeviceService |
Device block/kill signal management |
SessionReportService |
Report ZIP generation + Blob upload |
GraphTokenService |
MS Graph token acquisition for Autopilot device validation |
BlockedVersionService |
Version-based block/kill rules with wildcard and ceiling patterns |
GalacticNotificationService |
Persistent in-app notifications for Galactic Admins (survives page reloads) |
HealthCheckService |
Health checks for Storage, Processing, and Agent binary availability |
PreviewWhitelistService |
Private Preview tenant whitelist with 5-min cache |
ResendEmailService |
Transactional emails via Resend.com for Preview notifications |
Event Processing Pipeline
Agent POST /api/agent/ingest (NDJSON+gzip)
│
├─ SecurityValidator.ValidateRequestAsync()
│ ├─ Tenant existence & suspension
│ ├─ Bootstrap token gate (if present)
│ ├─ Certificate validation against Intune CAs
│ ├─ Rate limiting (sliding window)
│ ├─ Hardware whitelist
│ └─ Autopilot device validation (optional)
│
├─ BlockedDeviceService.IsBlockedAsync() → kill/block signal
│
├─ ParseNdjsonGzipRequest() → decompress + parse events
│
├─ TableStorageService.StoreEventsBatchAsync()
│
├─ ClassifyEvents()
│ ├─ Extract geo-location
│ ├─ Track app installs → AppInstallSummaries table
│ └─ Detect enrollment completion/failure
│
├─ UpdateSessionStatusAsync() → merge session row
│
├─ RuleEngine.AnalyzeSessionAsync() (on enrollment end)
│ └─ StoreRuleResultAsync() → RuleResults table
│
└─ SignalR broadcasts:
├─ "eventReceived" → tenant-{tenantId}
├─ "sessionStatusChanged" → tenant-{tenantId}
└─ "ruleResultReceived" → tenant-{tenantId}
Web Frontend
Technology Stack
- Framework: Next.js 15.1.6 (App Router)
- Language: TypeScript 5.7.3
- UI: React 18.2.0 + Tailwind CSS 3.4.17 (dark mode via
classstrategy) - Auth: MSAL.js 3.28 (
@azure/msal-browser+@azure/msal-react) - Real-time:
@microsoft/signalr10.0.0 - Maps: Leaflet 1.9.4 + react-leaflet
- Compression: fflate 0.8.2
Page Routes
| Path | Purpose | Auth | Role |
|---|---|---|---|
/ |
Landing page with platform stats | Public | — |
/dashboard |
Session list, real-time updates | Yes | Any |
/sessions/[sessionId] |
Session detail, event timeline, analysis | Yes | Any |
/diagnosis/[sessionId] |
Rule analysis results | Yes | Any |
/settings |
Tenant configuration (10+ sections) | Yes | Tenant Admin |
/admin-configuration |
Global admin settings | Yes | Galactic Admin |
/gather-rules |
Gather rule CRUD | Yes | Tenant Admin |
/analyze-rules |
Analyze rule CRUD | Yes | Tenant Admin |
/ime-log-patterns |
IME pattern management | Yes | Tenant Admin |
/fleet-health |
App metrics, fleet analytics | Yes | Tenant Admin |
/usage-metrics |
Tenant usage analytics | Yes | Tenant Admin |
/platform-metrics |
Platform-wide metrics | Yes | Galactic Admin |
/audit |
Audit log viewer | Yes | Tenant Admin |
/health-check |
Backend health status | Yes | Galactic Admin |
/geographic-performance |
Geo map of deployments | Yes | Galactic Admin |
/progress |
Real-time enrollment progress tracking | Yes | Tenant Admin |
/preview |
Private Preview waitlist/approval page | Yes | Unapproved tenants |
/changelog |
Platform change log & known issues | Public | — |
/roadmap |
Planned features & current focus areas | Public | — |
/about |
Platform introduction & quick links | Public | — |
/privacy |
Privacy policy & data handling | Public | — |
/terms |
Terms of use & legal disclaimers | Public | — |
/go/[code] |
Bootstrap script generator (validates code → returns PS1) | Public | — |
/docs/[section] |
Documentation | Public | — |
State Management
React Context API (no Redux/Zustand):
| Context | Purpose |
|---|---|
AuthContext |
MSAL + user info + role detection (galactic/tenant admin) |
SignalRContext |
WebSocket connection, group subscriptions, auto-reconnect |
TenantContext |
Current tenant ID (persisted to localStorage) |
NotificationContext |
Toast notifications with auto-dismiss + deduplication |
ThemeContext |
Dark mode toggle (localStorage) |
API Communication
lib/authenticatedFetch.ts– Wrapsfetch()with Bearer token, 401 retry with token refreshhooks/useAuthenticatedFetch.ts– React hook with loading/error state- Tenant isolation: All endpoints append
?tenantId={id}
Key UI Components
| Component | Location | Purpose |
|---|---|---|
SessionTable |
dashboard/components/ |
Paginated, filterable, sortable session list |
EventTimeline |
sessions/[id]/components/ |
Phase-grouped event visualization |
PhaseTimeline |
sessions/[id]/components/ |
Visual phase progress with live activity |
ProtectedRoute |
components/ |
Auth guard with role-based access |
ScriptExecutions |
components/ |
Script output viewer |
PerformanceChart |
components/ |
Time-series metrics chart |
UI Patterns
- Settings pages use
error(string|null) +successMessage(string|null) state for notifications - Notifications rendered at top of
<main>content area "use client"directive on interactive components- Expand/collapse sections with guard blocks
UnsavedChangesModalprevents navigation with unsaved form state
Shared Library
Target: .NET Standard 2.0 (compatible with both net48 Agent and net8.0 Backend)
Model Categories
| Namespace | Key Classes | Purpose |
|---|---|---|
Models/Enrollment/ |
EnrollmentEvent, SessionRegistration, EnrollmentPhase, BootstrapSession |
Core enrollment data |
Models/Config/ |
AgentConfigResponse, CollectorConfiguration, TenantConfiguration, AdminConfiguration |
Configuration hierarchy |
Models/Rules/ |
GatherRule, AnalyzeRule, ImeLogPattern, RuleResult |
Rules engine |
Models/Metrics/ |
UsageMetrics, PlatformStats, AppInstallSummary |
Analytics |
Models/Diagnostics/ |
DiagnosticsLogPath, AgentErrorReport |
Diagnostics collection |
ApiModels.cs |
Request/Response pairs for all endpoints | API contracts |
Constants.cs |
Table names, API endpoints, event types, defaults | Shared constants |
Key Enums
EnrollmentPhase: Unknown(-1), Start(0), DevicePreparation(1), DeviceSetup(2), AppsDevice(3), AccountSetup(4), AppsUser(5), FinalizingSetup(6), Complete(7), Failed(99)Severity: Debug(0), Info(1), Warning(2), Error(3), Critical(4)- 50+ Event Types:
phase_transition,app_install_completed,enrollment_complete,enrollment_failed,esp_state_change,performance_snapshot,script_completed,gather_result, etc.
Data Model
Azure Table Storage (23 Tables)
| Table | PartitionKey | RowKey | Purpose |
|---|---|---|---|
Sessions |
TenantId | SessionId | Enrollment sessions |
Events |
SessionId | Timestamp_Sequence | Individual events |
AdminConfiguration |
"GlobalConfig" | "config" | Platform-wide settings |
TenantConfiguration |
TenantId | "config" | Per-tenant settings |
GatherRules |
TenantId | RuleId | Data collection rules |
AnalyzeRules |
TenantId | RuleId | Issue detection rules |
ImeLogPatterns |
TenantId | PatternId | IME log regex patterns |
RuleResults |
TenantId | SessionId_RuleId | Analysis findings |
UsageMetrics |
TenantId | MetricDate | Daily usage snapshots |
AppInstallSummaries |
TenantId | SessionId_AppName | Per-app install data |
PlatformStats |
"Global" | "stats" | Cumulative platform stats |
AuditLogs |
TenantId | Timestamp_Id | Admin action audit trail |
BootstrapSessions |
TenantId / "CodeLookup" | ShortCode | OOBE bootstrap tokens |
BlockedDevices |
TenantId | SerialNumber | Blocked devices |
SessionReports |
TenantId | ReportId | User-submitted reports |
GalacticAdmins |
"GalacticAdmins" | UPN | Platform-level admins |
TenantAdmins |
TenantId | UPN | Tenant-level admins |
PreviewWhitelist |
"Preview" | TenantId | Preview access gate |
UserActivity |
TenantId | UserId | User login tracking |
RuleStates |
TenantId | RuleId | Rule enable/disable state |
PreviewConfig |
"Preview" | "config" | Preview feature config |
BlockedVersions |
"BlockedVersions" | Pattern | Version block/kill rules (wildcards, ceilings) |
GalacticNotifications |
"GalacticNotifications" | InvertedTicks_Id | Persistent in-app notifications for Galactic Admins |
Azure Blob Storage
- Diagnostics container: Agent-uploaded ZIP packages (
AgentDiagnostics-{sessionId}-{ts}.zip) - Session reports container: User-submitted report ZIPs
- Platform stats blob: Cached JSON for landing page
Entity Relationships
Session (1) ──► (N) EnrollmentEvent
Session (1) ──► (N) RuleResult
Session (1) ──► (N) AppInstallSummary
Session (1) ──► (0..1) SessionReport
Session (N) ◄── (1) TenantConfiguration
TenantConfiguration (N) ◄── (1) AdminConfiguration (inherits defaults)
GatherRule ──► (agent executes) ──► EnrollmentEvent (gather_result)
AnalyzeRule ──► (backend evaluates) ──► RuleResult
ImeLogPattern ──► (agent matches) ──► EnrollmentEvent (various types)
Security Architecture
Authentication Layers
Agent → Backend (device auth):
-
MDM Client Certificate (primary)
- Agent discovers Intune MDM cert in LocalMachine\My store
- Sent via TLS (mTLS), forwarded as
X-ARR-ClientCertby Azure App Service - Validated against embedded Intune intermediate + root CA certificates
-
Bootstrap Token (pre-MDM, OOBE)
- Admin creates time-limited token+code via web UI
- Agent sends as
X-Bootstrap-Tokenheader - Bypasses cert/rate/hardware validation
-
Hardware Headers (supplementary)
X-Device-SerialNumber,X-Device-Manufacturer,X-Device-Model- Used for whitelist validation and device identification
Web → Backend (user auth):
- Microsoft Entra ID multi-tenant JWT via
AuthenticationMiddleware - Dynamic OIDC metadata per tenant (cached 24h)
- Claims:
tid(tenant),upn(user),oid(object ID)
Validation Pipeline (per agent request)
ValidateSecurityAsync() (SecurityValidationExtensions.cs)
├─ 1. Tenant existence & suspension check (cheapest first)
├─ 2. Bootstrap token gate (if present → short-circuit)
├─ 3. Certificate validation against Intune CA chain
├─ 4. Rate limiting (sliding window, 1-min, per-device)
├─ 5. Hardware whitelist check (optional per tenant)
└─ 6. Autopilot device validation via MS Graph (optional per tenant)
Tenant Data Isolation
- All Table Storage queries filtered by
PartitionKey = TenantId - SignalR groups:
tenant-{tenantId}for scoped broadcasts - JWT
tidclaim determines tenant for web requests
Roles
| Role | Scope | Capabilities |
|---|---|---|
| Galactic Admin | Platform-wide | Global config, all tenants, platform metrics, health checks |
| Tenant Admin | Single tenant | Tenant config, rules, admin management, device blocking |
| User | Single tenant | Read-only dashboard, session detail view |
Real-Time Communication
SignalR Integration
- Hub:
autopilotmonitor - Transport: WebSocket with auto-reconnect (0s, 2s, 10s, 30s backoff)
- Token factory: Fresh JWT per connection attempt
Groups & Events
| Group | Events | Triggered By |
|---|---|---|
tenant-{tenantId} |
newSession, sessionStatusChanged, eventReceived, ruleResultReceived |
Event ingestion, session registration |
galactic-admins |
newSession (all tenants), platform stats updates |
Cross-tenant broadcasts |
Client-Side (SignalRContext)
- Auto-rejoin groups after reconnect
- Components subscribe on mount, unsubscribe on unmount
- Connection state exposed for UI indicators
Rules Engine
Three Rule Types
| Type | Execution | Purpose |
|---|---|---|
| Gather Rules | Agent-side | Collect data from registry, WMI, event logs, files, commands |
| Analyze Rules | Backend-side | Detect issues in enrollment events with confidence scoring |
| IME Log Patterns | Agent-side | Parse IME/AppWorkload/HealthScripts logs with regex |
Gather Rules
- Collector Types:
registry,wmi,eventlog,file,command_allowlisted,logparser - Triggers:
startup,interval,phase_change,on_event - Security: Command allowlist enforced by
GatherRuleGuards - Output: Emits
gather_resultevent with collected data
Analyze Rules
- Conditions: Match events by source, signal, operator, value, with event correlation
- Confidence Scoring:
BaseConfidence+ConfidenceFactors(signal × weight), threshold at 40 - Output:
RuleResultwith explanation, remediation steps, related docs - Trigger: Evaluated after enrollment completion/failure
IME Log Patterns
- Categories:
always(all phases),currentPhase,otherPhases - Actions: 50+ (e.g.,
setCurrentApp,updateStateInstalled,espPhaseDetected) - Regex: Named capture groups,
{GUID}placeholder for Intune policy IDs - Tracked Logs:
IntuneManagementExtension*.log,AppWorkload*.log,AgentExecutor*.log,HealthScripts*.log
Rule Distribution
- Individual JSON files in
rules/gather/,rules/analyze/,rules/ime-log-patterns/ - GitHub Actions validates against JSON Schema + combines into
rules/dist/ - Combined files embedded as resources in Functions assembly
- Served to agents via
/api/agent/config, manageable via web UI per tenant
Session Lifecycle
Three Completion Paths
Path 1: IME Pattern Completion
IME logs show all apps completed → Hello wait → enrollment_complete
Path 2: ESP Exit + Hello (Composite)
ESP final exit (event 62407) → Hello wait (300s) → enrollment_complete
Path 3: Desktop Arrival (No-ESP / WDP v2)
explorer.exe detected under real user → Hello wait → enrollment_complete
ESP & Hello Tracking
- ESP Events: Shell-Core event log (62404=Hello wizard start, 62407=ESP exit/WhiteGlove)
- Hello Events: User Device Registration log (300=NGC success, 301=NGC failure)
- Hello Wait: 30s for wizard start → 300s for completion → timeout
- Policy Check: WHfB policy registry poll every 10s; skip Hello wait if not configured
Failure Detection
- Terminal failures (Failure, Abort, WhiteGlove_Failed) → immediate
enrollment_failed - Recoverable failures (Timeout) → 60s grace period before marking failed
- Auth failures → circuit breaker (max 5 attempts or configurable timeout)
- Device-Only ESP: 5-min timer after DeviceSetup exit; if no AccountSetup → device-only classification
WhiteGlove (Pre-Provisioning)
- Part 1:
whiteglove_complete→ persist state, exit gracefully (no self-destruct, session preserved) - Part 2: Agent restarts on next boot, detects
whiteglove.completemarker →whiteglove_resumed - Session survives across reboot; sequence counter persisted
State Persistence (Crash Recovery)
| File | Purpose |
|---|---|
enrollment-state.json |
ESP flags, Hello state, completion signals |
ime-tracker-state.json |
Phase order, seen apps, file positions |
session.id / session.seq |
Session identity + event sequence counter |
enrollment-complete.marker |
Cleanup retry flag if previous cleanup failed |
Cleanup & Self-Destruct
- Stop collectors, run shutdown analyzers
- Drain event spool, final upload
- Upload diagnostics ZIP (if configured)
- Remove Scheduled Task, delete binaries/config/spool
- Optionally reboot device (
RebootOnComplete)
Configuration Hierarchy
AdminConfiguration (global, single row in Azure Table)
│ GlobalRateLimitRequestsPerMinute (default 100)
│ CollectorIdleTimeoutMinutes (default 15)
│ AgentMaxLifetimeMinutes (default 360)
│ DiagnosticsGlobalLogPathsJson
│
└──► TenantConfiguration (per tenant, inherits/overrides)
│ Rate limiting (override or inherit global)
│ Hardware whitelist, Autopilot device validation
│ Collector intervals, Hello timeout
│ Diagnostics: UploadEnabled, LogPathsJson
│ Auth circuit breaker settings
│ Teams/Telegram notifications
│ Bootstrap token enablement
│
└──► AgentConfigResponse (delivered to agent via /api/agent/config)
│ ConfigVersion (currently 16)
│ CollectorConfiguration (nested)
│ AnalyzerConfiguration (nested)
│ GatherRules[] (merged built-in + tenant)
│ ImeLogPatterns[] (merged built-in + tenant)
│ DiagnosticsLogPaths[] (merged global + tenant)
└ Various flags and intervals
Caching: Both admin and tenant configs cached 5 minutes in-memory (IMemoryCache).
Diagnostics & Upload
Architecture (Post-Refactor)
- Old: Long-lived SAS URL stored in agent config → device stores in
remote-config.json - New:
DiagnosticsUploadEnabledboolean; agent callsPOST /api/agent/upload-urljust before upload - SAS URL never stored on device or in config, kept in memory only
Upload Flow
- Agent creates ZIP:
sessioninfo.txt+ agent logs + IME logs + configured paths - Agent calls
POST /api/agent/upload-url→ receives short-lived SAS URL (1h) - Agent uploads via
PUT {blobUrl}withx-ms-blob-type: BlockBlob - 3 retries with exponential backoff (2s, 4s, 8s); 401/403 = non-retryable
Diagnostics Log Paths
- Global paths:
AdminConfiguration.DiagnosticsGlobalLogPathsJson(built-in,IsBuiltIn=true) - Tenant paths:
TenantConfiguration.DiagnosticsLogPathsJson(custom,IsBuiltIn=false) - Merged list delivered to agent; security validated by
DiagnosticsPathGuards
Upload Modes
| Mode | Behavior |
|---|---|
Off (default) |
Disabled |
Always |
Upload on both success and failure |
OnFailure |
Upload only on enrollment failure |
Infrastructure & Deployment
Azure Resources
| Resource | Purpose |
|---|---|
| Azure Functions (Isolated Worker) | REST API, event processing, timer triggers |
| Azure Table Storage | 21 tables for sessions, events, config, rules, metrics |
| Azure Blob Storage | Diagnostics ZIPs, session reports, platform stats cache |
| Azure SignalR Service | WebSocket hub for real-time updates |
| Azure Static Web Apps | Next.js frontend hosting |
| Application Insights | Logging, telemetry, performance monitoring |
| Microsoft Entra ID | Multi-tenant OIDC authentication |
CI/CD (GitHub Actions)
| Workflow | Trigger | Purpose |
|---|---|---|
azure-static-web-apps-*.yml |
Push to main (Web changes) | Build + deploy Next.js to Azure Static Web Apps |
combine-rules.yml |
Changes to rules/, schema/, scripts/ |
Validate rules against JSON Schema, combine into dist/, auto-commit |
Environment Variables (Web)
NEXT_PUBLIC_ENTRA_CLIENT_ID # App registration client ID
NEXT_PUBLIC_ENTRA_REDIRECT_URI # Auth redirect (localhost:3000 dev)
NEXT_PUBLIC_ENTRA_POST_LOGOUT_REDIRECT_URI
NEXT_PUBLIC_API_BASE_URL # Backend URL (localhost:7071 dev)
NEXT_PUBLIC_PLATFORM_STATS_MANIFEST_URL
Build & Development
Build Commands
# .NET solution (Agent + Backend + Shared)
dotnet build AutopilotMonitor.sln --nologo -v quiet
# Backend tests
dotnet test src/Backend/AutopilotMonitor.Functions.Tests/
# Web frontend
cd src/Web/autopilot-monitor-web
npm install
npm run dev # Development server (localhost:3000)
npm run build # Production build
npx tsc --noEmit # Type checking only
# Rules validation + combine
cd rules && node scripts/combine.js
Agent CLI Arguments
--install Deploy via Scheduled Task (Intune package)
--run-gather-rules One-shot data collection, then exit
--console Enable console output
--cert-thumbprint {thumb} Override cert search
--tenant-id {id} Override registry tenant
--api-url {url} Override API endpoint (alias: --backend-api)
--bootstrap-token {token} Pre-MDM bootstrap auth
--ime-log-path {path} Override IME log folder
--ime-match-log {path} Write matched IME log lines to file (debug)
--replay-log-dir {path} Enable log replay mode
--replay-speed-factor {n} Compression factor (default 50)
--no-auth Disable cert auth
--no-cleanup Disable self-destruct
--reboot-on-complete Trigger reboot after enrollment
--new-session Start fresh session
--keep-logfile Preserve logs after cleanup
--await-enrollment Wait for MDM certificate before starting
--await-enrollment-timeout {min} MDM cert wait timeout (default 480min)
--disable-geolocation Skip geo-location detection
Key Design Conventions
| Convention | Details |
|---|---|
| Agent endpoint security | All agent endpoints use req.ValidateSecurityAsync() from SecurityValidationExtensions.cs |
| ConfigVersion | Tracks agent capability level (currently 16 = trace events for decision auditing) |
| Phase progression | Forward-only: DeviceSetup(1) → AccountSetup(2), no backward transitions |
| Phase isolation | App IDs seen in earlier phases are ignored in later phases (IME tracker) |
| Completion throttling | Max 1 completion_check event per source per minute |
| Sequence persistence | Saved every 50 events + on critical events; crash recovery uses spool ceiling |
| Settings UI | error + successMessage state for notifications at top of <main> |
| Maintenance timer | Runs every 2 hours (not daily, despite function name) |
| Agent versioning | Auto-incremented: 1.0.{BuildNumber} |