Fleet health alerts
Find out about the tablet with a dead battery before the morning shift does. This program checks every device and posts one message listing the ones that need attention. Run it every hour.
Needs: a service account with ViewDevices, a Slack incoming webhook or Microsoft Teams workflow
webhook, and .NET 10.
OHR_TOKEN=ohrs_… ALERT_WEBHOOK_URL=https://hooks.slack.com/services/… dotnet run fleet-health-alerts.cs3 device alert(s):• A0000A00043 (Airport kiosk) has not checked in for 11 hours• A0000A00070 (Downtown store) is at 9% battery and not charging• A0000A00081 (Downtown store) could not apply 1 setting(s)#:package OhrTech.Management@*// Posts a summary of devices that need attention to a Slack or Microsoft Teams channel.// Run it on a schedule (cron, a scheduled task, a GitHub Actions schedule).// OHR_TOKEN=ohrs_… ALERT_WEBHOOK_URL=https://hooks.slack.com/… dotnet run fleet-health-alerts.cs// Needs ViewDevices. Without ALERT_WEBHOOK_URL it prints the summary instead.using System.Text;using System.Text.Json.Nodes;using OhrTech.Management;
var offlineAfter = TimeSpan.FromHours(6);const int lowBattery = 15;
using var api = OhrManagementClient.ForServiceAccount( Environment.GetEnvironmentVariable("OHR_TOKEN") ?? throw new InvalidOperationException("Set OHR_TOKEN to a service account token."));
var alerts = new List<string>();foreach (var d in await api.ListDevicesAsync()){ var name = $"{d.Serial ?? $"device {d.DeviceId}"} ({d.FleetName ?? "no fleet"})";
if (d.LastCheckIn is { } seen && DateTimeOffset.UtcNow - seen is var away && away > offlineAfter) alerts.Add($"{name} has not checked in for {(away.TotalDays >= 2 ? $"{away.TotalDays:0} days" : $"{away.TotalHours:0} hours")}"); if (d.BatteryLevel is < lowBattery && !d.Charging) alerts.Add($"{name} is at {d.BatteryLevel}% battery and not charging"); if (d.SettingsErrors > 0) alerts.Add($"{name} could not apply {d.SettingsErrors} setting(s)"); if (d.AgentOutdated) alerts.Add($"{name} is waiting to update the OhrTech Agent");}
if (alerts.Count == 0) return;
var text = $"{alerts.Count} device alert(s):\n• " + string.Join("\n• ", alerts);var webhook = Environment.GetEnvironmentVariable("ALERT_WEBHOOK_URL");if (string.IsNullOrEmpty(webhook)){ Console.WriteLine(text); return;}
// Slack incoming webhooks and Teams workflow webhooks both accept {"text": "…"}.using var http = new HttpClient();var body = new JsonObject { ["text"] = text }.ToJsonString();(await http.PostAsync(webhook, new StringContent(body, Encoding.UTF8, "application/json"))).EnsureSuccessStatusCode();How it works
Section titled “How it works”One call, GET /v1/devices, returns every device’s last check-in, battery and charging state,
settings errors and whether the OhrTech Agent is behind. The program applies your thresholds and
posts only when something needs attention.
Run it on a schedule
Section titled “Run it on a schedule”0 * * * * OHR_TOKEN=ohrs_… ALERT_WEBHOOK_URL=https://hooks.slack.com/… dotnet run /opt/ohr/fleet-health-alerts.csOr as a GitHub Actions schedule, a Windows scheduled task or a Kubernetes CronJob. Pin the service account to the networks it runs from.
Make it yours
Section titled “Make it yours”- Change
offlineAfterandlowBattery, or alert per fleet to the team that owns it. - Before alerting that a device is offline, ask it to check in (
POST /v1/devices/{id}/check-in) and look again a minute later. - Open a ticket in your help desk instead of posting a message.