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.
292 lines
12 KiB
Markdown
292 lines
12 KiB
Markdown
<!-- docmeta
|
|
purpose: howto
|
|
audience: adopter
|
|
status: current
|
|
summary: Point the asset reporter and EventSaver at your own ShopDB, by Intune or by hand.
|
|
loadwith:
|
|
-->
|
|
|
|
# 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](FLEET-ARCHITECTURE.md) 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](COLLECTOR-PAYLOADS.md). 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](COLLECTOR-AUTH.md) |
|
|
| 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](COLLECTOR-AUTH.md) 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.
|
|
|
|
```powershell
|
|
# 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:
|
|
|
|
```powershell
|
|
# 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:
|
|
|
|
```powershell
|
|
& "$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:
|
|
|
|
```powershell
|
|
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:
|
|
|
|
```powershell
|
|
# 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:
|
|
|
|
```powershell
|
|
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](COLLECTOR-CLIENT-SETUP.md).
|
|
|
|
---
|
|
|
|
## Part 2: EventSaver
|
|
|
|
### What you have to decide
|
|
|
|
[EVENTSAVER](EVENTSAVER.md) is what EventSaver is, how it picks slides and how to
|
|
diagnose it. Read it first; adopting it adds only three decisions:
|
|
|
|
1. **Your feed URL**, in `EventSaver.ini`:
|
|
`url=https://<your-shopdb>/api/slides/feed?surface=shopfloor`.
|
|
2. **Ship the ini with the binary.** There is no compiled-in fallback, so a
|
|
missing ini is a blank screen. Check any `.scr` you 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).
|
|
3. **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:
|
|
|
|
```powershell
|
|
(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:
|
|
|
|
```powershell
|
|
# 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
|
|
|
|
```powershell
|
|
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](EVENTSAVER.md) 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".
|