Corporate Tools tab on PC Details, gated by endpoints.deploy and liveness. Starts already-registered scheduled tasks; the agent's ScheduledTaskPolicy allowlist remains the authority on what may actually run. Dell Command | Update split into two actions (Scan, Apply Updates) because they have very different consequences. Apply Updates carries a persistent breakglass warning in the card naming Tanium as the primary update utility, rendered between the row title and description so it is visible at all times rather than only in the confirm dialog. Destructive actions keep the typed RUN confirmation. Fix scheduledtask_run being stripped by Configuration Policy: the intersection in ALLOWED_ACTIONS_FOR dropped it on any endpoint whose effective policy was non-empty, so every Corporate Tools click failed with "not permitted by policy" regardless of the caller's role. Corporate tools are role-gated only, via POLICY_EXEMPT_ACTIONS; policy keeps authority over monitors and service/process primitives. Fix blank PC Details page: watch(detailtabs) evaluates its source during setup and read canDeploy before its const declaration, throwing a TDZ ReferenceError that aborted setup with no API request made. Mapped-drive refresh moves to the tray. SYSTEM cannot probe user-scoped drives, so the service requests a re-probe over a named event and waits for the spool collectedat stamp to advance before collecting. The probe task is registered as a ShopFloor user principal; the Users-group principal remains only as a degraded fallback since a group member cannot start it.
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
|
|
}
|