# 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:///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/' ` -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:///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".