Endpoint telemetry, PC Detail rework and a checked backup/restore path
This commit is contained in:
119
agent/utilities/shopdb-dellupdates.ps1
Normal file
119
agent/utilities/shopdb-dellupdates.ps1
Normal file
@@ -0,0 +1,119 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
ShopDB agent utility: reports pending Dell Command | Update items.
|
||||
|
||||
.DESCRIPTION
|
||||
Discovered and executed by PowerShellUtilityRunner on the telemetry cadence.
|
||||
Emits a single JSON object on stdout, which the agent attaches to the report
|
||||
as `dellupdates`. Never throws and never writes anything but JSON: a failure
|
||||
here must not break a telemetry run, so every error path still emits a valid
|
||||
object with a `status` explaining why the list is empty.
|
||||
|
||||
THIS SCRIPT DOES NOT SCAN. It only reads the log a previous scan wrote. The
|
||||
scan itself is fired by the \ShopDB-DellCommandScan scheduled task, which is
|
||||
what pins dcu-cli.exe's -outputLog to the path below. Keeping the two apart
|
||||
is deliberate: a telemetry collector that shelled out to dcu-cli would block
|
||||
the cadence for minutes and hammer Dell's servers every 15 minutes.
|
||||
|
||||
OUTPUT CONTRACT (keep in sync with plugins/endpoints/plugin.py):
|
||||
status unknown | ok | error - "unknown" means no scan has ever run,
|
||||
which is NOT the same as "ok" with an empty list (known clean).
|
||||
scannedat ISO-8601 UTC timestamp of the scan log's last write, or null.
|
||||
count total pending updates found.
|
||||
updates up to $MaxUpdates entries of { name, severity, version }.
|
||||
truncated true when count exceeded $MaxUpdates.
|
||||
|
||||
Dell's own long description text is deliberately NOT carried: it is
|
||||
sentence-length marketing prose per update, which would add roughly an order
|
||||
of magnitude to every report for no operational value. Name plus severity is
|
||||
what an operator decides on.
|
||||
#>
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Cap the list rather than the payload. A machine that is years behind can have
|
||||
# dozens of pending updates, and an unbounded list would bloat every report on
|
||||
# the cadence. Mirrors the MaxReportedConnections precedent in CommandExecutor:
|
||||
# a truncated answer is more useful than a rejected payload.
|
||||
$MaxUpdates = 25
|
||||
|
||||
# Must match the -outputLog path set in the \ShopDB-DellCommandScan task
|
||||
# definition. ShopDB-initiated scans write HERE rather than to Dell's default
|
||||
# location, which is what keeps them distinguishable from the corporate-managed
|
||||
# DCU tasks that also run on these machines.
|
||||
$ScanLog = Join-Path $env:ProgramData 'ShopDB\EndpointAgent\dellcommand\scan.xml'
|
||||
|
||||
function Write-Result {
|
||||
param($Status, $ScannedAt, $Count, $Updates, $Truncated)
|
||||
# Compress so the payload carries no pretty-printing whitespace. Depth 3
|
||||
# covers the nested updates array.
|
||||
[pscustomobject]@{
|
||||
status = $Status
|
||||
scannedat = $ScannedAt
|
||||
count = $Count
|
||||
updates = @($Updates)
|
||||
truncated = [bool]$Truncated
|
||||
} | ConvertTo-Json -Depth 3 -Compress
|
||||
}
|
||||
|
||||
try {
|
||||
if (-not (Test-Path -LiteralPath $ScanLog)) {
|
||||
# No scan has run yet. This is the normal state for a freshly adopted
|
||||
# endpoint, not an error, but it must stay distinct from "scanned and
|
||||
# found nothing" or the UI would claim a machine is up to date when
|
||||
# nobody has ever checked.
|
||||
Write-Result -Status 'unknown' -ScannedAt $null -Count 0 -Updates @() -Truncated $false
|
||||
return
|
||||
}
|
||||
|
||||
$logFile = Get-Item -LiteralPath $ScanLog
|
||||
$scannedAt = $logFile.LastWriteTimeUtc.ToString('o')
|
||||
|
||||
[xml]$report = Get-Content -LiteralPath $ScanLog -Raw
|
||||
|
||||
# dcu-cli's scan report shape has varied across DCU versions, so select the
|
||||
# update nodes by name anywhere in the tree rather than by a fixed path.
|
||||
$nodes = @($report.SelectNodes('//update'))
|
||||
if ($nodes.Count -eq 0) {
|
||||
$nodes = @($report.SelectNodes('//Update'))
|
||||
}
|
||||
|
||||
$updates = foreach ($node in $nodes) {
|
||||
# Attribute casing also varies by version; take whichever is present and
|
||||
# fall back rather than emitting a null-filled row.
|
||||
$name = $node.name
|
||||
if (-not $name) { $name = $node.Name }
|
||||
if (-not $name) { $name = $node.display }
|
||||
if (-not $name) { continue }
|
||||
|
||||
$severity = $node.urgency
|
||||
if (-not $severity) { $severity = $node.Urgency }
|
||||
if (-not $severity) { $severity = $node.criticality }
|
||||
|
||||
$version = $node.version
|
||||
if (-not $version) { $version = $node.Version }
|
||||
|
||||
[pscustomobject]@{
|
||||
name = [string]$name
|
||||
severity = if ($severity) { [string]$severity } else { 'unknown' }
|
||||
version = if ($version) { [string]$version } else { $null }
|
||||
}
|
||||
}
|
||||
|
||||
$updates = @($updates)
|
||||
$count = $updates.Count
|
||||
$truncated = $count -gt $MaxUpdates
|
||||
if ($truncated) {
|
||||
$updates = $updates[0..($MaxUpdates - 1)]
|
||||
}
|
||||
|
||||
Write-Result -Status 'ok' -ScannedAt $scannedAt -Count $count `
|
||||
-Updates $updates -Truncated $truncated
|
||||
}
|
||||
catch {
|
||||
# Emit a valid object rather than failing: the runner would log a non-JSON
|
||||
# utility and drop it, which looks identical to "never ran" on the server.
|
||||
# An explicit error status tells an operator the scan log exists but could
|
||||
# not be read, which is a different problem from no scan at all.
|
||||
Write-Result -Status 'error' -ScannedAt $null -Count 0 -Updates @() -Truncated $false
|
||||
}
|
||||
Reference in New Issue
Block a user