120 lines
4.7 KiB
PowerShell
120 lines
4.7 KiB
PowerShell
<#
|
|
.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
|
|
}
|