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:
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
|
||||
Reference in New Issue
Block a user