# 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.
