Files
shopdb-flask/plugins/printers/client/DEPLOYING-DRIVERS.md
cproudlock 8cedf674fb
Some checks failed
CI / naming (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / migrations-mysql (push) Has been cancelled
CI / backend (push) Has been cancelled
Resolve a driver by vendor, and converge a bay's printers from ShopDB
Two rows now cover 41 of 44 printers. printerdrivers could only bind a driver to
ONE modelnumberid, so the HP and Xerox universal drivers - which between them
cover almost the whole floor - would have needed 21 near-duplicate rows pointing
at the same package. That is a table nobody keeps true, and it is why 42 of 44
printers resolved no driver at all.

printerdrivers gains vendorid, and resolution runs most-specific-first: the
printer's model, then its vendor, then the pre-vendorid convention of matching
the vendor word in the driver's name so a site that populated the table before
the column existed does not silently lose every driver on upgrade. A row that
names a vendor is never matched by its text, because a mis-set vendor resolving
to the wrong package is worse than resolving to none.

Six rows now resolve 44 of 44 printers at the reference site, and the DesignJet
correctly takes its own driver over the HP universal one.

Set-ShopdbPrinters.ps1 is the client half: ask for-host, create the queues that
are missing, record the desired default. It NEVER removes a queue - a bad minute
from the API must not take printers away from a working bay - and it never
fetches a driver, because downloading 48 MB while somebody waits to print is the
wrong moment. The common scope stages those.

Apply-ShopdbDefaultPrinter.ps1 applies the default in the USER's context, which
is the only context that can: SYSTEM cannot set a per-user default for somebody
else. It also turns off "Let Windows manage my default printer", without which
Windows silently overwrites the choice the next time anyone prints elsewhere -
a fix that undoes itself within a day.

VALIDATED ON WINDOWS 11 AGAINST A LIVE SHOPDB, not only by tests. Printers were
assigned to a MACHINE; a PC controlling it, holding no rows of its own, created
both queues bound to the right universal drivers, recorded the default and set
it, and a second run changed nothing. The first attempt failed with
"Relationship types are not seeded - run: flask seed reference-data", which is
the deployment trap the plan predicted, caught by an explicit error rather than
silently resolving nothing.
2026-08-19 10:24:53 -04:00

145 lines
5.7 KiB
Markdown

# Deploying the printer driver set
The driver set is a package: `Install-ShopdbPrinterDrivers.ps1`, the
single-driver worker it wraps, a `drivers.json` naming each driver and where its
files are, and the driver packages themselves.
Staging drivers is deliberately SEPARATE from assigning printers. Drivers are
large, change rarely and are identical across a fleet; assignments are small,
per-bay and change often. Keeping them apart means creating a queue never waits
on a download, and a driver never has to be fetched at the moment someone is
trying to print.
## The shape
```
ShopdbPrinterDrivers\
Install-ShopdbPrinterDrivers.ps1 the whole set, manifest driven
Install-ShopdbPrinterDriver.ps1 one driver (this does the work)
drivers.json what this site deploys
drivers\
hp_upd_ps\ xerox_gpd\ hp_designjet\ zebra_zt411\ ...
```
`drivers.json` paths may be relative to the package or absolute. A site whose
packages already live on a share points at the share and ships only the two
scripts and the manifest.
## GE-Enforce, in the `common` scope
Every shop-floor PC gets every driver, once. After the first cycle each run is a
`Get-PrinterDriver` check per driver and nothing else, so the cost is a few
milliseconds, not a re-install.
```json
{
"_comment": "Stage the site's printer drivers. Runs in-cycle because the share is only mounted then. Idempotent: a driver already present is skipped.",
"Name": "ShopDB printer drivers",
"Type": "PS1",
"Script": "scripts/Install-ShopdbPrinterDrivers.ps1",
"DetectionMethod": "Always"
}
```
**It must be a manifest entry, not its own scheduled task.** The SFLD share is
mounted only for the duration of the enforcement cycle; off-cycle the paths
simply do not exist and every run logs "package not found" forever.
## Azure Machine Configuration / DSC
The script answers a compliance question, which is what makes it a clean `Script`
resource: `-TestOnly` reports whether every driver in the manifest is present and
exits 0 or 1 without changing anything.
```powershell
Configuration ShopdbPrinterDrivers
{
Import-DscResource -ModuleName PSDesiredStateConfiguration
Node localhost
{
Script PrinterDrivers
{
GetScript = {
@{ Result = (Get-PrinterDriver | Select-Object -ExpandProperty Name) -join ', ' }
}
TestScript = {
$p = Start-Process -FilePath 'powershell.exe' -PassThru -Wait -WindowStyle Hidden `
-ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File',
'C:\ProgramData\ShopDB\Drivers\Install-ShopdbPrinterDrivers.ps1','-TestOnly'
return ($p.ExitCode -eq 0)
}
SetScript = {
Start-Process -FilePath 'powershell.exe' -Wait -WindowStyle Hidden `
-ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-File',
'C:\ProgramData\ShopDB\Drivers\Install-ShopdbPrinterDrivers.ps1'
}
}
}
}
```
Deliver the package to `C:\ProgramData\ShopDB\Drivers` however that estate
already delivers files - a Win32 app, a File resource, or the imaging step.
## Intune
Package the folder as a Win32 app.
- Install: `powershell.exe -NoProfile -ExecutionPolicy Bypass -File Install-ShopdbPrinterDrivers.ps1`
- Detection: a script running the same file with `-TestOnly`, exit 0 = detected
- Run as SYSTEM. Adding a printer driver has required administrator rights since
the 2021 print hardening, and SYSTEM satisfies it.
## Why not have the assignment client fetch drivers
It was considered and rejected. A bay would then download a driver at the moment
a printer is assigned, which is the worst time: someone is waiting, the share may
be unmounted, and a 48 MB package would be pulled per bay per change. Staging the
set in `common` makes assignment a queue creation and nothing more.
## One driver per package, named exactly
`drivers.json` carries the driver name as its INF declares it - `Add-PrinterDriver`
matches that string and nothing else. The names verified on Windows for the
reference site's fleet:
| driver | covers |
|---|---|
| `HP Universal Printing PS` | HP office printers |
| `Xerox Global Print Driver PCL6` | Xerox office printers |
| `HP DesignJet T1700dr V4` | DesignJet plotters (a v4 class driver) |
| `ZDesigner ZT411-300dpi ZPL` | Zebra ZT411 labels |
| `EPSON TM-C3500` | Epson ColorWorks labels |
| `DTC4500e Card Printer` | HID FARGO card printer |
## The other half: assigning printers
Staging drivers is only delivery. `Set-ShopdbPrinters.ps1` is what makes a bay's
queues match ShopDB, and `Apply-ShopdbDefaultPrinter.ps1` applies the default in
the user's context. Two manifest entries, both `DetectionMethod: Always`:
```json
{
"_comment": "Create the queues this bay is assigned. Converges: existing queues are left alone, and nothing is ever removed.",
"Name": "ShopDB printers",
"Type": "PS1",
"Script": "scripts/Set-ShopdbPrinters.ps1",
"DetectionMethod": "Always"
}
```
The default printer is per-user, so SYSTEM cannot set it for the person logged
on. `Set-ShopdbPrinters.ps1` records it in `HKLM:\SOFTWARE\GE\ShopDB`
`DefaultPrinter`, and `Apply-ShopdbDefaultPrinter.ps1` runs as the user - at
logon, and on a repeat if the site wants drift corrected.
Order matters on a new bay: drivers, then queues, then the default. Each step is
a no-op once satisfied, so running all three every cycle costs a few registry
reads.
Verified end to end on Windows 11 against a live ShopDB: printers assigned to a
MACHINE, a PC controlling it and holding no rows of its own, and the bay created
both queues with the right universal driver, recorded the default, and set it -
then a second run changed nothing.