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.
127 lines
4.1 KiB
PowerShell
127 lines
4.1 KiB
PowerShell
# 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
|