A site adopting ShopDB had to be handed two files and told what to edit in them. Both are now the product's, and one of them the server writes for you. GET /api/computers/client-script (admin) returns Report-AssetToShopDB.ps1 with this site's values already in it: site_base_url becomes the -ApiUrl default and the new computers_routableranges setting becomes -AllowedRanges. Only the PARAMETER DEFAULTS are substituted - the copy in plugins/computers/client/ stays runnable, so there is no second version to drift from the first - and everything stamped stays overridable by argument or registry, because a bay may need to differ from its site. Settings > Computers > Asset reporter edits the ranges, downloads the script and shows its SHA-256. The collector key is deliberately not stamped in, and a test fails if it ever is. That file lands on every shop-floor PC, and a token spread across hundreds of bays cannot be rotated quietly; it stays in the registry, provisioned per ADOPTING-AT-ANOTHER-SITE.md. The routable ranges are the last thing that was hardcoded in that script. They are now a setting, so West Jefferson's two CIDRs move out of source code and into that site's own configuration - which is what ADR-015 asks for - and a site that sets nothing still works, because the script falls back to the NIC carrying the default route. EventSaver joins it in plugins/slides/client/, source only: EventSaver.cs and EventSaver.ini, no compiled .scr - a binary is a release asset, like the installer exe. The share path that was compiled into Config.Folder is gone. It used to be the fallback when the ini was missing, which silently pointed a new site at the reference site's file server; it is now empty, and failing visibly beats displaying another site's slides. Verified by compiling the edited source in the Windows VM with the in-box csc.exe: 15,872 bytes, exit 0. Also: the DSC example in the adoption guide gains a CollectorRanges resource and stops passing -ApiUrl to a script that already reads BaseUrl from the registry the same example writes, and the guide points at the generated download instead of hand-editing a URL. The contract test caught the endpoint importing shopdb directly for the version string, which ADR-002 forbids a plugin from doing. The product and contract versions are in app.config now, which a plugin reads through current_app. Adds docs/proposals/printer-assignment.md: assign printers to a PC in ShopDB and let the bay install them, with what the fleet data says about drivers - HP and Xerox cover 41 of 44 printers with universal drivers, there are no Brother printers at all despite 208 files of Brother inkjet drivers in the installer, and printerdrivers holds one row pointing at a per-model folder instead of a universal driver.
162 lines
7.3 KiB
Markdown
162 lines
7.3 KiB
Markdown
# Proposal: assign printers to a PC in ShopDB, let the PC install them
|
|
|
|
Status: PROPOSED. Not built.
|
|
Author: planning session 2026-08-18.
|
|
|
|
## 1. What this is
|
|
|
|
Today a printer reaches a shop-floor PC because a person walks up to it, opens
|
|
the printer installer, finds the printer on a floor plan and clicks it. That is
|
|
fine for someone choosing a printer, and wrong for a bay whose printers are a
|
|
property of the bay.
|
|
|
|
This proposal makes the assignment data: edit a PC in ShopDB, tick the printers
|
|
that belong on it, mark one default. The PC converges on its next GE-Enforce
|
|
cycle - installing what is missing and setting the default - and keeps
|
|
converging, so a reimaged bay comes back with its printers and a bay that drifts
|
|
is corrected.
|
|
|
|
The map installer stays, for the case it is actually good at: a person at an
|
|
unmanaged or office PC picking a printer that nobody assigned.
|
|
|
|
## 2. Why it is worth doing
|
|
|
|
- **The assignment becomes a record.** "Which printers does bay 2107 have" is a
|
|
question ShopDB can answer, and today it cannot.
|
|
- **A reimage stops costing a visit.** The bay reinstalls its own printers.
|
|
- **Drift is corrected, not just detected.** A queue deleted by a user comes
|
|
back.
|
|
- **It removes the walk-up from the common case.** The installer's map remains
|
|
for the uncommon one.
|
|
|
|
## 3. What already exists
|
|
|
|
Most of the model is in place, which is why this is a small feature rather than
|
|
a project.
|
|
|
|
| piece | state |
|
|
|---|---|
|
|
| PC to printer link | `defaultprinter` asset relationship, seeded by `flask seed reference-data` |
|
|
| Default lookup | `GET /api/printers/pc-default?machine=NNNN` |
|
|
| Host lookup | `GET /api/computers/by-hostname/<hostname>` |
|
|
| Printer model | `Printer.modelnumberid` - populated for 44 of 44 printers at the reference site |
|
|
| Driver record | `PrinterDriver` (name, `location` as SMB path or URL, optional `modelnumberid`) |
|
|
| Per-printer install path | `Printer.installpath` |
|
|
| Batch install | `GET /api/printers/install-batch?printerids=1,2,3` |
|
|
| Client transport | GE-Enforce manifest entries, `Type=PS1`, running as SYSTEM every cycle |
|
|
| Silent driver staging | Proven in `PrinterInstaller.iss`: trust the catalog's signing cert, then `pnputil /add-driver` |
|
|
|
|
## 4. What has to be built
|
|
|
|
### 4.1 One endpoint
|
|
|
|
```
|
|
GET /api/printers/for-host/<hostname>
|
|
```
|
|
|
|
Returns the printers assigned to that PC and which is default, each with what a
|
|
client needs to install it: queue name, host or IP, port, driver name, driver
|
|
location.
|
|
|
|
Resolved by hostname, not machine number: the collector already upserts PCs by
|
|
hostname, and an office PC has no machine number.
|
|
|
|
**Per-PC assignments must NOT go in the manifest.** Manifests are keyed by scope
|
|
and PC type and sync broadly; putting per-PC rows there would leak every bay's
|
|
configuration to every bay and grow without limit. One manifest entry runs one
|
|
script that asks the API what THIS host gets - the mirror image of
|
|
`Report-AssetToShopDB.ps1`.
|
|
|
|
### 4.2 Two columns on `printerdrivers`
|
|
|
|
- `drivername` - the driver's exact name as the INF declares it, e.g.
|
|
`HP Universal Printing PCL 6`. `Add-PrinterDriver` needs it verbatim, and a
|
|
mismatch is the usual failure. Deriving it by parsing the INF on hundreds of
|
|
bays is fragile; a human confirming it once in ShopDB is not.
|
|
- `installmethod` - `pnputil` or `dpinst`. See section 6: if Brother really is
|
|
absent from the fleet, everything is `pnputil` and this column can wait.
|
|
|
|
### 4.3 UI on the PC form
|
|
|
|
A printer picker writing `defaultprinter` (one) and an assignment list (many).
|
|
`AssetRelationships.vue` already edits relationships; this is a narrowed case of
|
|
it.
|
|
|
|
### 4.4 One client script, in two contexts
|
|
|
|
`Set-ShopdbPrinters.ps1`, shipped in `plugins/printers/client/` beside the
|
|
contract it consumes, and run as a manifest entry with `DetectionMethod=Always`.
|
|
|
|
*As SYSTEM, every cycle:*
|
|
|
|
1. `GET /api/printers/for-host/$env:COMPUTERNAME`
|
|
2. For each assigned printer with no queue: trust the driver catalog's cert,
|
|
`pnputil /add-driver`, create the port, create the queue
|
|
3. Write the desired default to `HKLM:\SOFTWARE\GE\ShopDB DefaultPrinter`
|
|
4. Ensure the per-user task exists
|
|
|
|
*In the user's context, at logon and on a repeat:*
|
|
|
|
5. Read that value, compare with the current default, set it if it differs, and
|
|
clear "Let Windows manage my default printer" - otherwise Windows silently
|
|
overrides the choice the next time someone prints elsewhere
|
|
|
|
The default printer is per-user state, which is the only reason this needs two
|
|
contexts. Everything else is machine state and belongs to the cycle.
|
|
|
|
Converge, do not reinstall: when the state matches, the script does nothing.
|
|
Nothing here needs the manifest to know when a printer changes, because the
|
|
desired state is fetched, not declared.
|
|
|
|
## 5. Decisions to take before writing code
|
|
|
|
1. **Never remove a queue by default.** A transient API failure would otherwise
|
|
strip printers fleet-wide. Deletion is an explicit opt-in, per PC.
|
|
2. **Enforced or set-once for the default?** Re-applying every cycle overrides a
|
|
user who chose their own default - correct for a locked bay, irritating on an
|
|
office PC. Set-once is Active Setup or RunOnce. Make it a per-PC-type flag
|
|
rather than one global answer.
|
|
3. **Failure is silent and safe**: unreachable API means change nothing, log,
|
|
exit 0 - the convention `Report-AssetToShopDB.ps1` already follows.
|
|
|
|
## 6. What the fleet data says, and the one prerequisite
|
|
|
|
The reference site's 44 printers are HP 26, Xerox 15, Zebra 1, HID 1, Epson 1.
|
|
|
|
- **HP and Xerox are 41 of 44, and both have true universal drivers** (HP UPD,
|
|
Xerox Global Print Driver). One driver record each serves every queue of that
|
|
make.
|
|
- **There are no Brother printers at all**, yet the installer carries 208 files
|
|
of per-model Brother MFC-J inkjet drivers. Those are host-based GDI devices
|
|
with no Printer-class INF, which is the only reason a second staging method
|
|
(DPInst) exists. If production confirms no Brother, that payload and that code
|
|
path can both go.
|
|
- **Zebra, HID and Epson are one printer each**, and the HP DesignJet plotter is
|
|
a fourth special case - a PostScript device the UPD does not cover.
|
|
|
|
**Prerequisite: populate `printerdrivers`.** It currently holds ONE row, and it
|
|
points at a per-model folder (`HP LaserJet Pro M607 Driver`) rather than the
|
|
universal driver - the opposite of how a UPD should be used. The table needs
|
|
roughly four rows: HP UPD, Xerox GPD, one per oddity, and DesignJet when its
|
|
payload is restored. Nothing in this proposal works until a printer can resolve
|
|
to a driver.
|
|
|
|
## 7. Deployment constraint that shapes the design
|
|
|
|
**The SFLD share is mounted only during GE-Enforce's cycle.** Any work touching
|
|
a share path must run as a manifest entry inside that cycle, never as its own
|
|
scheduled task. The failure is silent - the task reports 0 processed, 0
|
|
installed, 0 failed - and it has cost a session before.
|
|
|
|
This is why driver staging belongs in the cycle even though the per-user default
|
|
does not, and why "the assignment script schedules a task that installs drivers"
|
|
is the wrong shape.
|
|
|
|
## 8. What this does not change
|
|
|
|
- The printer installer keeps working, for walk-up and self-service.
|
|
- Nothing about how printers are modelled, mapped or reported.
|
|
- The collector contract.
|
|
- Sites not running GE-Enforce: the same endpoint suits an Intune remediation or
|
|
a DSC `Script` resource, since it is a plain HTTP GET and a PowerShell script.
|