main was 16 days and 259 commits behind the line the product is actually built on, at contract 0.30.0 against 0.35.0, with no per-PC credential code at all. It was neither current nor curated - just stale. This brings it forward and, at the same time, makes it mean something: main is what a receiving site installs, and nothing else. WHAT IT CARRIES. Twelve plugins - backups, computers, endpoints, guides, knowledgebase, machines, network, notifications, printers, slides, tools, warranty - plus applications, which is manifest-less core and always ships. The only manifest dependency inside that set is endpoints on computers, and both are in it, so the closure is self-contained. WHAT IT DOES NOT. employees, geenforce, measuringtools, printedparts and usb stay on the site line. So do the Windows installer tree, the VM rehearsal helpers, the agent tooling config, one site's data loader, the internal audit and findings records, and the planner spreadsheet. 1,135 tracked files against 1,438. A REMOVAL THAT BREAKS THE APP IS NOT A REMOVAL, and two nearly shipped: deploy/site-profile-universal.json still named all seventeen plugins. It is the file docs/DEPLOY.md tells a site to provision from, so a fresh install would have failed at `flask plugin apply-profile` with PluginError - the first thing a receiving site does, broken by the branch meant to serve them. It now names the twelve that exist, and its comment says the two must agree. agent/ was never refreshed. Whoever populated this branch copied shopdb/, plugins/, frontend/ and tests/ from the site line and left the agent tree at its September state: 22 C# and PowerShell sources missing, including AgentCredentials, EnrollmentService, AgentHttpFactory and the fleet-alert client, plus one file the site line had deleted still present. The old tree is internally consistent, so it compiles, and nothing in CI builds it - every gate was green over a server that speaks enrollment and per-PC credentials to a bundled agent source that cannot. Resynced whole. DECISIONS THIS ENCODES, rather than defers: - The plugin-authoring lab builds printedparts, whose manifest depends on employees, so it could not stay. The lab remains on the site line and main ships no build-along tutorial. That is a real loss for an adopting developer and is recorded here rather than discovered later. - Recognition, Training and Recertification notifications are no longer seeded. Each splits one notification into a card per employee and renders a photo, which needs a plugin this branch does not bundle. Seeding them would offer an author a type that cannot resolve anybody. A site that installs employees gets them from that plugin's own seed. - Windows/IIS is documented as the legacy path throughout, and the README leads with Docker, because that is what a receiving site gets. The API description is regenerated for THIS branch - 347 paths and 483 operations, down from 415 and 573 - so the published contract describes what is here rather than what the site line serves. docs/CONFIG.md lost the two measuring-tool label keys the code no longer declares, which two lanes had decided opposite ways. Suite: 3124 passed, 6 skipped. Naming and style gates pass. The docs gates run rather than skip, and every index line count is derived from the file it names.
12 KiB
Adopting the fleet tools at another site
Two things run on shop-floor PCs and talk to a ShopDB instance: the asset reporter, which posts what a PC is to the collector API, and EventSaver, a screensaver that shows a slideshow served by ShopDB.
Neither is site-specific by design. What is site-specific is the URL, the credential, and which PCs it runs on - and all three are inputs, not code.
Getting them onto a PC is your site's job, not ShopDB's. ShopDB serves the data; something else lays the files down and keeps them there. This page covers the two delivery paths that do not assume a tool you may not have: Microsoft Intune, and doing it by hand. If you already run a configuration-enforcement tool on the floor, the end state below is what it has to produce.
Read FLEET-ARCHITECTURE first if you have not: it is one page and it is what makes the rest of this make sense.
Where these files come from
| Artifact | Where it is |
|---|---|
Report-AssetToShopDB.ps1 |
plugins/computers/client/, so it versions with the collector contract it implements. It names no site: the server comes from HKLM:\SOFTWARE\GE\ShopDB BaseUrl or -ApiUrl |
Report-PrintersToShopDB.ps1 |
plugins/printers/client/. Optional - it records what print queues a bay actually has |
EventSaver (EventSaver.cs, EventSaver.ini) |
plugins/slides/client/. One C# file that builds with the in-box .NET Framework compiler, so a site can rebuild it rather than trust a binary. The compiled EventSaver.scr is a release asset, not a file in the repository. Neither file names a site |
Part 1: the asset reporter
What it does
Runs as SYSTEM and POSTs what the PC is - hostname, BIOS serial, PC type,
logged-in user, make and model, OS, last boot, every routable NIC, the machine
number where one is configured - to
POST https://<your-shopdb>/api/collector/computers. The payload contract is
COLLECTOR-PAYLOADS. A field your site does not collect
is simply absent; the server upserts on hostname and leaves the rest alone.
What you must provide
| Input | Where it comes from |
|---|---|
| Server URL | -ApiUrl, or HKLM:\SOFTWARE\GE\ShopDB value BaseUrl. A script downloaded from Settings has your URL already in it |
| A credential | HKLM:\SOFTWARE\GE\ShopDB value CollectorKey for the shared key, or the PC's own credential once it has enrolled. Never stamped into the script. See COLLECTOR-AUTH |
| Your routable ranges | A server setting, collector_corporate_ranges. The bay reports every NIC and takes no view; the server picks which one is the PC's address |
The reporter still accepts -AllowedRanges and reads a CollectorRanges
registry value, and ignores both, logging a note when either is set. Choosing
the NIC on the bay was wrong - a bay cannot know a site's addressing - so it
moved to the server. The parameters remain only so an existing scheduled task
does not fail on an unknown argument. Set the server setting.
Shortcut: download it pre-configured. Settings > Computers > Asset reporter generates the script with your server's URL already in the parameter defaults, and shows its SHA-256. The credential is deliberately not included.
The credential to start with
Mint a managed personal access token scoped to collector.ingest and nothing
else, so a token recovered off a shop-floor PC cannot read your asset register.
Plan to move off it onto per-PC credentials.
COLLECTOR-AUTH is the whole story and the only page that
describes it.
Deploying it
The three paths below are alternatives - pick one. All of them end with the same two facts on the PC: the script exists somewhere it can run from, and the credential is in the registry.
Option A: Microsoft Intune
A1. The key, as a platform script. Devices > Scripts and remediations > Platform scripts > Add > Windows 10 and later. Run as SYSTEM, do not run in the 64-bit context only.
# Set-ShopdbCollectorKey.ps1 (Intune platform script, runs once per device)
New-Item -Path 'HKLM:\SOFTWARE\GE\ShopDB' -Force | Out-Null
Set-ItemProperty -Path 'HKLM:\SOFTWARE\GE\ShopDB' -Name CollectorKey `
-Value 'shopdb_pat_REPLACE_WITH_YOUR_TOKEN'
exit 0
Putting a token in an Intune script body means anyone who can read the Intune
configuration can read the token. That is an argument for the collector.ingest
scope, not against Intune: the blast radius of that token is one API. Restrict
the registry key's ACL to SYSTEM and Administrators so an interactive user on the
bay cannot read it.
A2. The reporter, as a remediation on a schedule. Devices > Scripts and remediations > Remediations. A remediation is the natural fit because it re-runs on a schedule, which is exactly what reporting is.
Detection script - forces the remediation to run every cycle:
# Always "not compliant": reporting is not a state to converge on, it is an
# event that should happen on every schedule tick.
exit 1
Remediation script - the reporter itself, with your URL:
& "$PSScriptRoot\Report-AssetToShopDB.ps1" `
-ApiUrl 'https://shopdb.example.net/api/collector/computers'
exit 0
Assign it with a daily schedule. Run as SYSTEM, 64-bit.
A3. Intune with DSC (Machine Configuration). If your estate is already governed by Azure Machine Configuration, express the credential as configuration and leave the reporting to a scheduled task, because a report is an event and DSC converges state:
Configuration ShopdbCollector
{
Import-DscResource -ModuleName PSDesiredStateConfiguration
Node localhost
{
Registry CollectorKey
{
Key = 'HKEY_LOCAL_MACHINE\SOFTWARE\GE\ShopDB'
ValueName = 'CollectorKey'
ValueData = 'shopdb_pat_REPLACE_WITH_YOUR_TOKEN'
ValueType = 'String'
Ensure = 'Present'
}
Registry CollectorUrl
{
Key = 'HKEY_LOCAL_MACHINE\SOFTWARE\GE\ShopDB'
ValueName = 'BaseUrl'
ValueData = 'https://shopdb.example.net'
ValueType = 'String'
Ensure = 'Present'
}
# ...and a Script resource whose SetScript registers the scheduled task
# below (option B has the three lines). Pass it no -ApiUrl: BaseUrl
# above is where the script reads the server from, and one source beats
# two that can disagree.
}
}
Do not add a CollectorRanges resource. That value is read and ignored; the
ranges are a server setting now.
Option B: manual
For a pilot, a single bay, or an estate with no management plane:
# As administrator, once per PC.
New-Item -Path 'C:\ProgramData\ShopDB' -ItemType Directory -Force | Out-Null
Copy-Item .\Report-AssetToShopDB.ps1 'C:\ProgramData\ShopDB\'
New-Item -Path 'HKLM:\SOFTWARE\GE\ShopDB' -Force | Out-Null
Set-ItemProperty -Path 'HKLM:\SOFTWARE\GE\ShopDB' -Name CollectorKey `
-Value 'shopdb_pat_REPLACE_WITH_YOUR_TOKEN'
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument @'
-NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\ShopDB\Report-AssetToShopDB.ps1" -ApiUrl "https://shopdb.example.net/api/collector/computers"
'@
$trigger = New-ScheduledTaskTrigger -Daily -At 7am
Register-ScheduledTask -TaskName 'ShopDB asset report' -Action $action -Trigger $trigger `
-User 'SYSTEM' -RunLevel Highest -Force
Start-ScheduledTask -TaskName 'ShopDB asset report'
Lock down C:\ProgramData\ShopDB if you put it there: a script a user can edit,
run by SYSTEM on a schedule, is a local privilege escalation. Break inheritance
and grant Administrators and SYSTEM only.
Verifying, and what to do when it fails
Open Computers in the web UI and find the hostname: it should be there with its
serial, type and a last-reported timestamp within one schedule tick. The same
answer over the API needs a logged-in session, because the read side is
permissioned (computers.view) even though the ingest side is not:
Invoke-RestMethod -Uri 'https://shopdb.example.net/api/computers/by-hostname/<HOSTNAME>' `
-Headers @{ Authorization = "Bearer $jwt" }
The collector key does NOT work here - it grants collector.ingest and nothing
else, which is the point of it. The script also logs to C:\Logs\Shopfloor.
Every failure mode and its fix is the table in
COLLECTOR-CLIENT-SETUP.
Part 2: EventSaver
What you have to decide
EVENTSAVER is what EventSaver is, how it picks slides and how to diagnose it. Read it first; adopting it adds only three decisions:
- Your feed URL, in
EventSaver.ini:url=https://<your-shopdb>/api/slides/feed?surface=shopfloor. - Ship the ini with the binary. There is no compiled-in fallback, so a
missing ini is a blank screen. Check any
.scryou were handed rather than built: an early build still has another site's file server baked in, which is exactly why the fallback was removed (ADR-015). - Which PCs get it. A screensaver over a live display is a defect. Exclude kiosks, lobby displays and metrology bays.
If your configuration tool enforces the ini by hash, your own hand edits on a bay revert - that is the feature working. Edit the copy your tool deploys from, and update the hash it detects on:
(Get-FileHash .\configs\EventSaver.ini -Algorithm SHA256).Hash
Deploying it
Option A: Intune
Package the three files as a Win32 app (EventSaver.scr, EventSaver.ini, and
the enable script) with this install command:
# install.ps1 for the Win32 app
Copy-Item .\EventSaver.scr 'C:\Windows\System32\EventSaver.scr' -Force
Copy-Item .\EventSaver.ini 'C:\Windows\System32\EventSaver.ini' -Force
exit 0
Detection rule: file C:\Windows\System32\EventSaver.scr exists and matches
the expected SHA-256, so a changed binary reinstalls.
Enable it per user with a configuration profile (Settings catalog > Screen
saver) or a user-context script setting the three values under option B. With
Machine Configuration/DSC, the two files are File resources - but DSC runs as
SYSTEM, so the per-user setting needs the logged-on user's hive or a
default-user template, which is usually more trouble than a login script.
Option B: manual
Copy-Item .\EventSaver.scr 'C:\Windows\System32\' -Force
Copy-Item .\EventSaver.ini 'C:\Windows\System32\' -Force # with YOUR url=
# Per user, in the user's own session:
Set-ItemProperty 'HKCU:\Control Panel\Desktop' -Name SCRNSAVE.EXE -Value 'C:\Windows\System32\EventSaver.scr'
Set-ItemProperty 'HKCU:\Control Panel\Desktop' -Name ScreenSaveActive -Value '1'
Set-ItemProperty 'HKCU:\Control Panel\Desktop' -Name ScreenSaveTimeOut -Value '480'
Those three values are the whole "enable" step in every option above: a screensaver is a per-user setting, so whatever deploys the binary still has to reach the user hive.
Verifying
Run EventSaver.scr /c on the PC - it reports which source it resolved and
where it is caching - then open that feed URL in a browser on the same PC. The
symptom table in EVENTSAVER reads the answers.
Which path to choose
| You have | Use |
|---|---|
| Intune across the estate | Option A. A remediation for the reporter, a Win32 app for EventSaver. |
| A configuration-enforcement tool on the floor | Its own idiom, producing the end state option B describes: the script on disk, the credential in the registry, a task as SYSTEM. |
| Neither, or a pilot of a few bays | Option B, then move to A once it earns its place. |
These are not exclusive, and on a real floor they usually are not. Where two tools both converge the same PC, decide which owns which layer before deploying both: the deciding question is which one you would look at first to answer "why is this bay wrong".