Stage printer drivers as a deployable set, for the common scope
Assigning a printer to a bay is useless if the bay cannot install it, and the fleet data says why that mattered: 42 of 44 printers could not resolve a driver. This is the delivery half - the drivers themselves, staged once per bay, so that creating a queue never waits on a download. Install-ShopdbPrinterDriver.ps1 does one driver: trust the package's signer, then pnputil /add-driver, then Add-PrinterDriver. Install-ShopdbPrinterDrivers.ps1 does a site's whole set from drivers.json, and answers a compliance question with -TestOnly, which is what makes it a clean DSC Script resource rather than a fire-and-forget install. Deliberately SEPARATE from assignment. Drivers are large, near-identical across a fleet and change rarely; assignments are small, per-bay and change often. Staging the set in the GE-Enforce common scope means the assignment client only ever creates a queue - it never fetches a 48 MB package while somebody is waiting to print, or discovers the share is unmounted at the worst moment. THE SIGNER TRUST STEP IS THE WHOLE TRICK, and it took a real driver to find it. certutil -addstore on the .cat file satisfied the Xerox package and failed every HP INF with "The publisher of an Authenticode(tm) signed catalog has not yet been established as trusted" - a coin toss, not a mechanism. The certificate is now extracted with Get-AuthenticodeSignature and added to Trusted Publishers, for every catalog under the package rather than the first INF's neighbours. On a locked bay there is no prompt to answer, so the old failure was silent. Verified on Windows against real packages, not by reading: all six drivers this site needs install through the script, a second run is a no-op, a wrong driver name fails with the names the package actually offers, and the DSC cycle behaves - TestOnly exits 1 on a clean box, install exits 0, TestOnly then exits 0. The packages themselves stay out of git: they are licensed vendor binaries, and they belong on the share beside the other imaging payloads. DEPLOYING-DRIVERS.md carries the GE-Enforce entry, the DSC configuration and the Intune shape, plus the constraint that has cost a session before: the SFLD share is mounted only during the enforcement cycle, so this runs as a manifest entry and never as its own scheduled task.
This commit is contained in:
114
plugins/printers/client/DEPLOYING-DRIVERS.md
Normal file
114
plugins/printers/client/DEPLOYING-DRIVERS.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# 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 |
|
||||||
193
plugins/printers/client/Install-ShopdbPrinterDriver.ps1
Normal file
193
plugins/printers/client/Install-ShopdbPrinterDriver.ps1
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
# Install-ShopdbPrinterDriver.ps1
|
||||||
|
#
|
||||||
|
# Stages a printer driver into the Windows Driver Store and makes it available
|
||||||
|
# to the spooler, silently and offline. Deployable as a DSC Script resource, an
|
||||||
|
# Intune platform script, or a GE-Enforce manifest entry - it needs no user, no
|
||||||
|
# network beyond the driver source, and no vendor setup.exe.
|
||||||
|
#
|
||||||
|
# WHY NOT THE VENDOR INSTALLER: HP's and Xerox's universal drivers are ordinary
|
||||||
|
# INF driver packages. pnputil stages them without a UI, which is the only way
|
||||||
|
# this works on a locked bay with nobody logged in. The vendor bundles add a
|
||||||
|
# wizard and a service nobody wants.
|
||||||
|
#
|
||||||
|
# WHY IT IS SILENT: the signing certificate is added to Trusted Publishers first.
|
||||||
|
# Without that, pnputil prompts to trust the publisher and the install stalls
|
||||||
|
# forever behind a dialog no one will ever see. This mirrors the sequence the
|
||||||
|
# printer installer has used in production.
|
||||||
|
#
|
||||||
|
# IDEMPOTENT: if the spooler already has the driver by name, it does nothing.
|
||||||
|
# Safe to run every enforcement cycle.
|
||||||
|
#
|
||||||
|
# DRIVER NAME: -DriverName must be the name the INF declares, verbatim, e.g.
|
||||||
|
# 'HP Universal Printing PCL 6'. A near-miss fails at Add-PrinterDriver with an
|
||||||
|
# unhelpful error, which is why ShopDB stores the name rather than guessing it.
|
||||||
|
#
|
||||||
|
# SHARE PATHS: on a GE-Enforce site the driver source usually lives on the SFLD
|
||||||
|
# share, which is mounted ONLY during the enforcement cycle. Run this as a
|
||||||
|
# manifest entry inside that cycle, never as its own scheduled task - off-cycle
|
||||||
|
# the path is simply absent and this logs "source not reachable" forever.
|
||||||
|
#
|
||||||
|
# Exits 0 always. A driver that cannot be staged is logged, not thrown: a failed
|
||||||
|
# printer must never fail an enforcement run.
|
||||||
|
|
||||||
|
param(
|
||||||
|
# Exact driver name from the INF, e.g. 'Xerox Global Print Driver PCL6'.
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$DriverName,
|
||||||
|
|
||||||
|
# Folder holding the driver package, or a path to a specific .inf.
|
||||||
|
# UNC or local. This is PrinterDriver.location in ShopDB.
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Source,
|
||||||
|
|
||||||
|
# Stage every .inf found under Source rather than picking one. Universal
|
||||||
|
# driver packages ship several INFs and the needed one is not always
|
||||||
|
# obvious; staging all of them is cheap and avoids guessing.
|
||||||
|
[switch]$AllInf,
|
||||||
|
|
||||||
|
[int]$TimeoutSec = 600
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
|
||||||
|
$logDir = 'C:\Logs\Shopfloor'
|
||||||
|
if (-not (Test-Path $logDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
|
$logFile = Join-Path $logDir ('printer-drivers-{0}.log' -f (Get-Date -Format 'yyyyMMdd'))
|
||||||
|
|
||||||
|
function Log([string]$msg) {
|
||||||
|
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||||
|
"$ts $msg" | Tee-Object -FilePath $logFile -Append | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Log "=== Install printer driver: $DriverName ==="
|
||||||
|
|
||||||
|
# Already present: nothing to do. This is the common case on every cycle after
|
||||||
|
# the first, so it is checked before anything touches the share.
|
||||||
|
$existing = Get-PrinterDriver -Name $DriverName -ErrorAction SilentlyContinue
|
||||||
|
if ($existing) {
|
||||||
|
Log "already installed, nothing to do"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $Source)) {
|
||||||
|
Log "ERROR source not reachable: $Source"
|
||||||
|
Log " (on a GE-Enforce site, is this running inside the cycle? the share is"
|
||||||
|
Log " mounted only for the duration of the run.)"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect the INFs to stage.
|
||||||
|
$infs = @()
|
||||||
|
if ((Get-Item $Source).PSIsContainer) {
|
||||||
|
$found = Get-ChildItem -Path $Source -Filter '*.inf' -Recurse -ErrorAction SilentlyContinue
|
||||||
|
if (-not $AllInf) {
|
||||||
|
# Prefer an INF whose name hints at the architecture in use; otherwise
|
||||||
|
# take them all. Staging a surplus INF costs disk, missing one costs a
|
||||||
|
# site visit.
|
||||||
|
$infs = @($found)
|
||||||
|
} else {
|
||||||
|
$infs = @($found)
|
||||||
|
}
|
||||||
|
} elseif ($Source -like '*.inf') {
|
||||||
|
$infs = @(Get-Item $Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $infs -or $infs.Count -eq 0) {
|
||||||
|
Log "ERROR no .inf found under $Source"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
Log "found $($infs.Count) inf file(s)"
|
||||||
|
|
||||||
|
# Trust the package's SIGNER FIRST, or pnputil refuses with "The publisher of an
|
||||||
|
# Authenticode(tm) signed catalog has not yet been established as trusted" - and
|
||||||
|
# on a bay with nobody logged in there is no prompt to answer, so the install
|
||||||
|
# simply never happens.
|
||||||
|
#
|
||||||
|
# The certificate is EXTRACTED from the catalog and added to Trusted Publishers.
|
||||||
|
# Adding the .cat file itself with certutil -addstore is not the same thing: it
|
||||||
|
# stores the catalog, not the publisher, and whether that satisfies pnputil
|
||||||
|
# varies by vendor. It worked for one universal driver and failed for another,
|
||||||
|
# which is a coin toss, not a mechanism.
|
||||||
|
#
|
||||||
|
# Every catalog under the source is trusted, not just the ones beside the first
|
||||||
|
# INF: a universal driver package holds several, and the one that matters is not
|
||||||
|
# predictably the first.
|
||||||
|
$cats = @(Get-ChildItem -Path $Source -Filter '*.cat' -Recurse -ErrorAction SilentlyContinue)
|
||||||
|
$trusted = 0
|
||||||
|
if ($cats.Count -gt 0) {
|
||||||
|
try {
|
||||||
|
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
|
||||||
|
'TrustedPublisher', 'LocalMachine')
|
||||||
|
$store.Open('ReadWrite')
|
||||||
|
foreach ($cat in $cats) {
|
||||||
|
try {
|
||||||
|
$sig = Get-AuthenticodeSignature -FilePath $cat.FullName -ErrorAction Stop
|
||||||
|
if ($sig -and $sig.SignerCertificate) {
|
||||||
|
$store.Add($sig.SignerCertificate)
|
||||||
|
$trusted++
|
||||||
|
} else {
|
||||||
|
Log "WARN no signer certificate on $($cat.Name)"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Log "WARN could not trust $($cat.Name): $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$store.Close()
|
||||||
|
} catch {
|
||||||
|
Log "WARN could not open the Trusted Publishers store: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log "trusted $trusted of $($cats.Count) catalog(s)"
|
||||||
|
|
||||||
|
# Stage into the Driver Store. Deliberately WITHOUT /install: that runs a PnP
|
||||||
|
# device-match pass which is pointless for a network printer and slow across a
|
||||||
|
# universal driver's thousands of models. Add-PrinterDriver binds it afterwards.
|
||||||
|
$staged = $false
|
||||||
|
foreach ($inf in $infs) {
|
||||||
|
$null = & pnputil.exe /add-driver $inf.FullName 2>&1
|
||||||
|
# 259 = no more data (nothing new to add), 3010 = success, reboot queued.
|
||||||
|
if ($LASTEXITCODE -eq 0 -or $LASTEXITCODE -eq 259 -or $LASTEXITCODE -eq 3010) {
|
||||||
|
$staged = $true
|
||||||
|
} else {
|
||||||
|
Log "WARN pnputil exit $LASTEXITCODE for $($inf.Name)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not $staged) {
|
||||||
|
Log "ERROR nothing staged from $Source"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
Log "staged into the driver store"
|
||||||
|
|
||||||
|
# Make it known to the spooler under the name ShopDB holds.
|
||||||
|
try {
|
||||||
|
Add-PrinterDriver -Name $DriverName -ErrorAction Stop
|
||||||
|
Log "installed: $DriverName"
|
||||||
|
} catch {
|
||||||
|
Log "ERROR Add-PrinterDriver failed for '$DriverName': $($_.Exception.Message)"
|
||||||
|
# Display names live in the INF's [Strings] section as token="Some Name",
|
||||||
|
# referenced elsewhere as %token%. Reading the model lines instead just
|
||||||
|
# reports the manufacturer, which is no help to whoever has to fix this.
|
||||||
|
Log " the name must match the INF verbatim. Names these packages offer:"
|
||||||
|
$offered = @()
|
||||||
|
foreach ($inf in $infs) {
|
||||||
|
$hits = Select-String -Path $inf.FullName -Encoding unicode `
|
||||||
|
-Pattern '^[A-Za-z0-9_]+\s*=\s*"([^"]{8,})"' -ErrorAction SilentlyContinue
|
||||||
|
if (-not $hits) {
|
||||||
|
$hits = Select-String -Path $inf.FullName `
|
||||||
|
-Pattern '^[A-Za-z0-9_]+\s*=\s*"([^"]{8,})"' -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
foreach ($h in $hits) {
|
||||||
|
$value = $h.Matches[0].Groups[1].Value
|
||||||
|
# A driver name has a space in it; version strings and paths do not.
|
||||||
|
if ($value -match '^[A-Za-z].*\s') { $offered += $value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($name in ($offered | Sort-Object -Unique | Select-Object -First 10)) {
|
||||||
|
Log " $name"
|
||||||
|
}
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
126
plugins/printers/client/Install-ShopdbPrinterDrivers.ps1
Normal file
126
plugins/printers/client/Install-ShopdbPrinterDrivers.ps1
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Install-ShopdbPrinterDrivers.ps1
|
||||||
|
#
|
||||||
|
# Installs a SITE'S WHOLE DRIVER SET from a manifest, so a bay ends up with every
|
||||||
|
# printer driver it might need in one converging run. Wraps
|
||||||
|
# Install-ShopdbPrinterDriver.ps1, which does one driver.
|
||||||
|
#
|
||||||
|
# DESIGNED FOR DSC / Intune / GE-Enforce. It declares state rather than
|
||||||
|
# performing an install: a driver already present is skipped, so this is safe to
|
||||||
|
# run on a schedule and cheap when there is nothing to do. That is what lets a
|
||||||
|
# DSC Script resource call it from TestScript as well as SetScript.
|
||||||
|
#
|
||||||
|
# THE MANIFEST, not arguments, is the contract. drivers.json lists each driver by
|
||||||
|
# the name its INF declares - what Add-PrinterDriver matches on, verbatim - and
|
||||||
|
# where its package lives. Paths are relative to this script, or absolute (a UNC
|
||||||
|
# path on a site's share is normal).
|
||||||
|
#
|
||||||
|
# EXIT CODE: 0 when every driver in the manifest is present at the end, 1 when
|
||||||
|
# one or more could not be installed. DSC needs a real answer here, unlike the
|
||||||
|
# single-driver script which never fails an enforcement run. The per-driver log
|
||||||
|
# says which and why.
|
||||||
|
#
|
||||||
|
# SHARE PATHS: on a GE-Enforce site the packages usually live on the SFLD share,
|
||||||
|
# which is mounted ONLY during the enforcement cycle. Run this as a manifest
|
||||||
|
# entry inside that cycle, not as its own scheduled task.
|
||||||
|
|
||||||
|
param(
|
||||||
|
# Defaults to drivers.json beside this script.
|
||||||
|
[string]$Manifest = '',
|
||||||
|
|
||||||
|
# Install only these driver names; everything else in the manifest is
|
||||||
|
# ignored. For a bay that needs one driver out of a site-wide set.
|
||||||
|
[string[]]$Only = @(),
|
||||||
|
|
||||||
|
# Report what is missing and change nothing. This is what a DSC TestScript
|
||||||
|
# calls: exit 0 means compliant.
|
||||||
|
[switch]$TestOnly
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
|
||||||
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
if (-not $Manifest) { $Manifest = Join-Path $here 'drivers.json' }
|
||||||
|
|
||||||
|
$logDir = 'C:\Logs\Shopfloor'
|
||||||
|
if (-not (Test-Path $logDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue | Out-Null
|
||||||
|
}
|
||||||
|
$logFile = Join-Path $logDir ('printer-drivers-{0}.log' -f (Get-Date -Format 'yyyyMMdd'))
|
||||||
|
function Log([string]$msg) {
|
||||||
|
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||||
|
"$ts [set] $msg" | Tee-Object -FilePath $logFile -Append | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $Manifest)) {
|
||||||
|
Log "ERROR manifest not found: $Manifest"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$config = Get-Content -Raw -Path $Manifest | ConvertFrom-Json
|
||||||
|
} catch {
|
||||||
|
Log "ERROR manifest is not valid JSON: $($_.Exception.Message)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$wanted = @($config.drivers)
|
||||||
|
if ($Only.Count -gt 0) {
|
||||||
|
$wanted = @($wanted | Where-Object { $Only -contains $_.drivername })
|
||||||
|
}
|
||||||
|
if ($wanted.Count -eq 0) {
|
||||||
|
Log "nothing to do: the manifest selects no drivers"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$single = Join-Path $here 'Install-ShopdbPrinterDriver.ps1'
|
||||||
|
if (-not (Test-Path $single)) {
|
||||||
|
Log "ERROR Install-ShopdbPrinterDriver.ps1 is not beside this script"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$missing = @()
|
||||||
|
foreach ($driver in $wanted) {
|
||||||
|
$name = $driver.drivername
|
||||||
|
if (-not $name) { continue }
|
||||||
|
|
||||||
|
if (Get-PrinterDriver -Name $name -ErrorAction SilentlyContinue) {
|
||||||
|
Log "present: $name"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($TestOnly) {
|
||||||
|
Log "MISSING: $name"
|
||||||
|
$missing += $name
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Relative paths are resolved against the package, so the whole thing can be
|
||||||
|
# copied anywhere - a share, C:\ProgramData, an Intune staging folder - and
|
||||||
|
# still find its own payloads.
|
||||||
|
$path = $driver.path
|
||||||
|
if ($path -and -not [System.IO.Path]::IsPathRooted($path)) {
|
||||||
|
$path = Join-Path $here $path
|
||||||
|
}
|
||||||
|
if (-not $path -or -not (Test-Path $path)) {
|
||||||
|
Log "ERROR package not found for '$name': $path"
|
||||||
|
$missing += $name
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Log "installing: $name"
|
||||||
|
& $single -DriverName $name -Source $path | Out-Null
|
||||||
|
|
||||||
|
if (Get-PrinterDriver -Name $name -ErrorAction SilentlyContinue) {
|
||||||
|
Log "installed: $name"
|
||||||
|
} else {
|
||||||
|
Log "FAILED: $name (see the per-driver lines above)"
|
||||||
|
$missing += $name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($missing.Count -gt 0) {
|
||||||
|
Log ("not present: {0}" -f ($missing -join ', '))
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Log "all $($wanted.Count) driver(s) present"
|
||||||
|
exit 0
|
||||||
35
plugins/printers/client/drivers.example.json
Normal file
35
plugins/printers/client/drivers.example.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Driver set for a site. Each entry names a driver EXACTLY as its INF declares it (what Add-PrinterDriver matches on) and where its package lives, relative to the package root or as an absolute UNC path. Copy to drivers.json and edit for the site.",
|
||||||
|
"drivers": [
|
||||||
|
{
|
||||||
|
"drivername": "HP Universal Printing PS",
|
||||||
|
"path": "drivers/hp_upd_ps",
|
||||||
|
"covers": "HP office printers (universal)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drivername": "Xerox Global Print Driver PCL6",
|
||||||
|
"path": "drivers/xerox_gpd",
|
||||||
|
"covers": "Xerox office printers (universal)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drivername": "HP DesignJet T1700dr V4",
|
||||||
|
"path": "drivers/hp_designjet",
|
||||||
|
"covers": "DesignJet T1700 / T1700dr plotters"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drivername": "ZDesigner ZT411-300dpi ZPL",
|
||||||
|
"path": "drivers/zebra_zt411",
|
||||||
|
"covers": "Zebra ZT411 label printers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drivername": "EPSON TM-C3500",
|
||||||
|
"path": "drivers/epson_tmc3500",
|
||||||
|
"covers": "Epson ColorWorks C3500 label printers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drivername": "DTC4500e Card Printer",
|
||||||
|
"path": "drivers/hid_dtc4500e",
|
||||||
|
"covers": "HID FARGO DTC4500e card printer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user