Mapped drives reported Unavailable on endpoints where the operator could see them fine. The data path was intact; the collection was running under the wrong token. Mapped drives are per logon token, so an elevated probe enumerates a different drive table than the interactive user. Collection: - Pin the user-context spool path so the probe and service agree on one location, and persist/overwrite it instead of deleting on read. - Refuse to collect while elevated (exit code 2) rather than emitting a confident wrong answer, and stamp elevated/username on the spool. - Reject spools stamped elevated or from an unexpected account on read, so an admin run cannot masquerade as valid. - Log netuse/smbmapping/userlistener counts so each run leaves evidence of what it actually saw. Install and launch context: - Move collection to a 'ShopDB user-context probe' scheduled task under S-1-5-32-545 at RunLevel Limited, the only mechanism that reliably runs in the operator's own session. Verify registration and record it in the install ledger. - Stop starting the tray as a child of the elevated installer; explorer.exe de-elevates without changing ACCOUNT, so an admin install produced an admin tray. The Run key only fires at logon, which left the tray dead on a machine already signed in, so add a transient 'ShopDB tray first launch' task that starts it once in the existing session and unregisters itself. - Hide the console window during --collect-usercontext. The agent is a console exe; the tray used to mask this with CreateNoWindow, but the scheduled task invokes it directly and flashed a window every cycle. Installer edits are in _install_script() in plugins/endpoints/api/siteagent.py, which generates the install.ps1 that actually ships. The standalone agent/installer/install-shopdbagent.ps1 was never shipped once the package moved to the UI builder and is deleted here to stop the divergence that caused an earlier fix to land in a file no endpoint ever ran. Versions: agent 1.10.1, tray 1.3.0. These share the pinned spool path and must be deployed together. Pre-existing test failures unrelated to this work (8, identical on a clean tree): docs publishable/versions, plugin contract symlink scan, import guard tamper check, security headers upload sandbox, and three endpoint timestamp tests.
178 lines
8.6 KiB
Markdown
178 lines
8.6 KiB
Markdown
# ShopDB Endpoint Agent
|
|
|
|
A Windows telemetry agent that reports endpoint state to a ShopDB server's
|
|
`endpoints` plugin. It runs as a Windows service in production and as a console
|
|
app for development and testing.
|
|
|
|
## Projects
|
|
|
|
- `ShopDbEndpointAgent.Core` - contracts, config, logging, collection, and the
|
|
resilient collector HTTP client. No host concerns; unit-testable.
|
|
- `ShopDbEndpointAgent` - the service host (generic host + Windows service
|
|
lifetime). Composes Core through DI. This is the telemetry runtime.
|
|
- `ShopDbEndpointAgent.Tray` - interactive user-session tray app: a system-tray
|
|
icon with "Send Status now" (on-demand report) and the 5-click issue reporter
|
|
(ServiceNow flow is still a scaffold). Separate process from the service,
|
|
because a SYSTEM service cannot draw a tray icon in a user session.
|
|
|
|
## Configuration
|
|
|
|
Runtime config is layered. Non-secret defaults live in `appsettings.json`
|
|
(shipped with the build). The collector key and base URL are provisioned out of
|
|
band in the registry and win over `appsettings.json`, so a distributed build
|
|
never carries the secret and the site can rotate the key without a rebuild.
|
|
|
|
### Registry (production)
|
|
|
|
Create these values under `HKLM\SOFTWARE\GE\ShopDB` (64-bit view):
|
|
|
|
| Value | Type | Meaning |
|
|
|----------------|--------|--------------------------------------------------|
|
|
| `BaseUrl` | String | `https://shopdb.example.net` (server root) |
|
|
| `CollectorKey` | String | The endpoint agent key from Settings -> Endpoints -> Key management |
|
|
|
|
Example (elevated PowerShell):
|
|
|
|
```
|
|
New-Item -Path 'HKLM:\SOFTWARE\GE\ShopDB' -Force | Out-Null
|
|
Set-ItemProperty -Path 'HKLM:\SOFTWARE\GE\ShopDB' -Name 'BaseUrl' -Value 'https://shopdb.example.net'
|
|
Set-ItemProperty -Path 'HKLM:\SOFTWARE\GE\ShopDB' -Name 'CollectorKey' -Value '<paste the generated key>'
|
|
```
|
|
|
|
### appsettings.json (all environments)
|
|
|
|
| Key | Default | Meaning |
|
|
|-------------------|-------------|------------------------------------------------|
|
|
| `baseurl` | "" | Dev fallback for the server root (registry wins). |
|
|
| `apikey` | placeholder | Dev fallback for the collector key (registry wins). |
|
|
| `cadenceseconds` | 900 | Report interval (15 min). |
|
|
| `jitterseconds` | 60 | Random +/- spread on the interval (anti-herd). |
|
|
| `adoptonfirstrun` | true | Send `adopt=true` on first contact only. |
|
|
| `utilitiespath` | "" | Directory of site PowerShell "agent utilities". |
|
|
| `logpath` | "" | Override the rolling log directory. |
|
|
|
|
For a dev box, set `baseurl` and `apikey` here and skip the registry.
|
|
|
|
## Running
|
|
|
|
Console (dev), one report then exit:
|
|
|
|
```
|
|
dotnet run --project ShopDbEndpointAgent -- --once
|
|
```
|
|
|
|
Console (dev), continuous cadence loop:
|
|
|
|
```
|
|
dotnet run --project ShopDbEndpointAgent -- --console
|
|
```
|
|
|
|
## Install as a Windows service
|
|
|
|
Publish self-contained, then register with `sc.exe` (elevated):
|
|
|
|
```
|
|
dotnet publish ShopDbEndpointAgent -c Release -r win-x64 --self-contained -o C:\ProgramData\ShopDB\EndpointAgent\bin
|
|
sc.exe create "ShopDBEndpointAgent" binPath= "C:\ProgramData\ShopDB\EndpointAgent\bin\shopdb-endpoint-agent.exe" start= auto DisplayName= "ShopDB Endpoint Agent"
|
|
sc.exe description "ShopDBEndpointAgent" "Reports endpoint telemetry to ShopDB."
|
|
sc.exe start "ShopDBEndpointAgent"
|
|
```
|
|
|
|
The service runs as LocalSystem by default, which it needs to read the Security
|
|
event channel and system WMI.
|
|
|
|
## Tray app (system-tray icon)
|
|
|
|
The tray is a per-user process, not a service, so it starts at sign-in rather
|
|
than under `sc.exe`. The generated `install.ps1` handles it: it copies
|
|
`shopdb-endpoint-tray.exe` next to the agent and registers a machine-wide logon
|
|
autostart (`HKLM\Software\Microsoft\Windows\CurrentVersion\Run\ShopDbEndpointTray`,
|
|
64-bit view). The installer deliberately does NOT start the tray itself: it runs
|
|
elevated, so any process it starts inherits an admin token, and mapped drives are
|
|
per logon token - an elevated tray probes the wrong drive table. The Run key only
|
|
fires at logon, which would leave the tray dead until sign-out on a machine
|
|
someone is already using, so the installer also registers a transient
|
|
`ShopDB tray first launch` scheduled task under `S-1-5-32-545` at `RunLevel
|
|
Limited`, runs it once, and unregisters it. That task is the only mechanism that
|
|
can reach into an existing interactive session without carrying the installer's
|
|
token. Pass `-NoTray` for a headless, service-only install.
|
|
|
|
User-context collection does not depend on the tray. The installer registers a
|
|
`ShopDB user-context probe` scheduled task against the Users group at
|
|
`RunLevel Limited`, which runs `shopdbagent.exe --collect-usercontext` in the
|
|
operator's own session at logon and every 30 minutes.
|
|
|
|
Right-click the tray icon for:
|
|
|
|
- `Send Status now` - reports this PC to ShopDB immediately so the monitor page
|
|
refreshes without waiting for the next cadence tick. It shells out to
|
|
`shopdbagent.exe --once`, reusing the exact production collection path; the
|
|
tray holds no collector key. A balloon tip reflects success or failure (on
|
|
failure, see the rolling agent log).
|
|
- `Report an issue...` - the ServiceNow ticket flow (scaffold; submission path
|
|
is an open decision).
|
|
|
|
A desktop shortcut to `shopdb-endpoint-tray.exe` can be added later for an
|
|
explicitly launchable ServiceNow surface.
|
|
|
|
## WebUI command execution model (security context)
|
|
|
|
The WebUI can send an endpoint an allowlisted command (server source of truth:
|
|
`plugins/endpoints/commands.py`; agent handler: `CommandExecutor`). The agent
|
|
service runs as LocalSystem, but not every command runs in that context. The
|
|
launch context is chosen per action so processes surface to the operator while
|
|
privileged operations keep the token they need:
|
|
|
|
- Generic `process_start` (for example DncMain, UDC) launches into the ACTIVE
|
|
CONSOLE USER's session at medium integrity, so the app is visible on the
|
|
operator's desktop. CyberArk/EPM brokers any elevation afterward.
|
|
- Generic `process_stop` / `process_status` run as SYSTEM (kill and probe).
|
|
- `service_start` / `service_stop` / `service_status` run as SYSTEM, because
|
|
service control requires the admin/SYSTEM token.
|
|
- `mtconnect_start` / `mtconnect_stop` / `mtconnect_status` all run as SYSTEM,
|
|
because MTConnect is the "UDC MTConnect Agent" Windows service and service
|
|
control requires the admin/SYSTEM token. `mtconnect_start` runs `install.bat`
|
|
(`Agent.exe install`) to install or repair the service, then ensures it is
|
|
running; `mtconnect_stop` stops the service; `mtconnect_status` queries it.
|
|
`run.bat` (`agent.exe run agent.cfg`) is a foreground TESTING launcher and is
|
|
deliberately NOT used by the agent.
|
|
- `system_restart` runs as SYSTEM and reboots the machine immediately via
|
|
`shutdown.exe /r /t 0` (no grace timer). It is a fixed action with no target,
|
|
gated server-side by the dedicated `endpoints.restart` permission on top of
|
|
`endpoints.command`, and fronted by a WebUI confirmation gate.
|
|
|
|
Do not route MTConnect through the generic `process_start` user-session path or
|
|
service control breaks. MTConnect is intentionally absent from the
|
|
`process_start` launch map for the same reason; the fixed `mtconnect_*` handler
|
|
owns it.
|
|
|
|
Behavior note for redeploys: the MTConnect service-control model is a runtime
|
|
change, so the agent must be rebuilt and redeployed for it to take effect.
|
|
Server-side command/allowlist/RBAC changes are picked up by the container
|
|
rebuild; rebuild and redeploy the agent whenever the runtime launch/control
|
|
logic itself changes.
|
|
|
|
## Diagnostics
|
|
|
|
- Rolling file log: `%ProgramData%\ShopDB\EndpointAgent\logs\YYYYMMDD-agent.log`
|
|
(14-day retention).
|
|
- Windows Event Log: agent runtime failures and unhandled exceptions are written
|
|
to the `Application` log under source `ShopDB Endpoint Agent`. The source is
|
|
created on first elevated run (or by the installer). Filter Event Viewer by
|
|
that source to see agent/system interop failures.
|
|
|
|
## Agent utilities (PowerShell seam)
|
|
|
|
If `utilitiespath` points at a directory, the runtime runs each `*.ps1` there
|
|
per report, parses its JSON stdout, and merges the result into the report. This
|
|
is the seam the future server-side "Agent configuration" settings page uses to
|
|
push site-authored collectors down. Scripts must be code-signed and
|
|
allow-listed before this is enabled in the field (rollout concern).
|
|
|
|
## Logging extensibility
|
|
|
|
All runtime code logs through `Microsoft.Extensions.Logging` (`ILogger`). Adding
|
|
a sink later (a ShopDB HTTP sink, Seq, syslog) is a host-builder change in
|
|
`Program.cs::ConfigureLogging` with no changes to the worker, collector, or
|
|
client.
|