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.
167 lines
5.7 KiB
PowerShell
167 lines
5.7 KiB
PowerShell
# Set-ShopdbPrinters.ps1
|
|
#
|
|
# Makes this PC's printers match what ShopDB says the bay should have. Asks
|
|
# GET /api/printers/for-host/<hostname> and creates any queue that is missing.
|
|
#
|
|
# WHY THE ASSIGNMENT IS NOT ON THIS PC: it is on the MACHINE, and reaches
|
|
# whichever PC controls it. A reimaged or swapped box inherits the bay's printers
|
|
# with nothing saved off the old one - the asset register is the backup.
|
|
#
|
|
# CONVERGES, does not install. A queue that already exists is left alone, so this
|
|
# is cheap to run every enforcement cycle and safe to run twice.
|
|
#
|
|
# NEVER REMOVES A QUEUE. If a printer disappears from the response - because the
|
|
# API had a bad minute, or someone unassigned it - the bay keeps printing. Taking
|
|
# printers away from a working bay because of a transient error is the one
|
|
# failure this must not have.
|
|
#
|
|
# DRIVERS ARE NOT FETCHED HERE. Install-ShopdbPrinterDrivers.ps1 stages the site's
|
|
# set in the common scope, once per bay. A queue is created against a driver that
|
|
# is already present; if it is not, that is logged and the printer is skipped,
|
|
# because downloading 48 MB while somebody waits to print is the wrong moment.
|
|
#
|
|
# THE DEFAULT PRINTER IS PER USER. This runs as SYSTEM and cannot set it for the
|
|
# logged-on person, so it records the desired default in HKLM and leaves applying
|
|
# it to a logon task. Without that, SYSTEM would set a default nobody sees.
|
|
#
|
|
# Exits 0 always: a printer problem must not fail an enforcement run.
|
|
|
|
param(
|
|
# ShopDB base URL. Empty resolves from HKLM:\SOFTWARE\GE\ShopDB BaseUrl,
|
|
# written by Install-GEEnforce.ps1 and already present wherever this runs.
|
|
[string]$BaseUrl = '',
|
|
|
|
# Defaults to this machine's name, which is what the collector upserts by.
|
|
[string]$Hostname = $env:COMPUTERNAME,
|
|
|
|
[int]$TimeoutSec = 30,
|
|
|
|
# Report what would change and touch nothing.
|
|
[switch]$WhatIfOnly
|
|
)
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
|
|
$logDir = 'C:\Logs\Shopfloor'
|
|
if (-not (Test-Path $logDir)) {
|
|
New-Item -ItemType Directory -Path $logDir -Force -ErrorAction SilentlyContinue | Out-Null
|
|
}
|
|
$logFile = Join-Path $logDir ('printers-{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
|
|
}
|
|
|
|
$REGPATH = 'HKLM:\SOFTWARE\GE\ShopDB'
|
|
|
|
if (-not $BaseUrl) {
|
|
foreach ($path in @($REGPATH, 'HKLM:\SOFTWARE\WOW6432Node\GE\ShopDB')) {
|
|
try {
|
|
if (Test-Path $path) {
|
|
$value = [string](Get-ItemProperty -Path $path -Name BaseUrl -ErrorAction Stop).BaseUrl
|
|
if ($value -and $value.Trim()) { $BaseUrl = $value.Trim(); break }
|
|
}
|
|
} catch {}
|
|
}
|
|
}
|
|
if (-not $BaseUrl) {
|
|
Log 'ERROR no ShopDB URL (HKLM:\SOFTWARE\GE\ShopDB BaseUrl or -BaseUrl). Skipping.'
|
|
exit 0
|
|
}
|
|
|
|
Log "=== Set printers for $Hostname ==="
|
|
|
|
$url = $BaseUrl.TrimEnd('/') + '/api/printers/for-host/' + [uri]::EscapeDataString($Hostname)
|
|
try {
|
|
$response = Invoke-RestMethod -Uri $url -Method Get -TimeoutSec $TimeoutSec
|
|
} catch {
|
|
# An unreachable server means "no information", not "no printers". Changing
|
|
# nothing is the only safe response.
|
|
Log "ERROR could not read $url : $($_.Exception.Message)"
|
|
exit 0
|
|
}
|
|
|
|
$payload = $response.data
|
|
if ($null -eq $payload) { $payload = $response }
|
|
$wanted = @($payload.printers)
|
|
$defaultid = $payload.defaultprinterid
|
|
|
|
if ($wanted.Count -eq 0) {
|
|
Log 'nothing assigned to this host'
|
|
exit 0
|
|
}
|
|
Log "assigned: $($wanted.Count) printer(s)"
|
|
|
|
$existing = @{}
|
|
foreach ($queue in (Get-Printer -ErrorAction SilentlyContinue)) {
|
|
$existing[$queue.Name] = $queue
|
|
}
|
|
|
|
$defaultname = ''
|
|
foreach ($printer in $wanted) {
|
|
$name = $printer.queuename
|
|
if (-not $name) { continue }
|
|
if ($printer.printerid -eq $defaultid) { $defaultname = $name }
|
|
|
|
if ($existing.ContainsKey($name)) {
|
|
Log "present: $name"
|
|
continue
|
|
}
|
|
|
|
$address = $printer.hostname
|
|
if (-not $address) { $address = $printer.ipaddress }
|
|
if (-not $address) {
|
|
Log "SKIP $name : no hostname or IP to point a port at"
|
|
continue
|
|
}
|
|
|
|
$drivername = $printer.drivername
|
|
if (-not $drivername) {
|
|
Log "SKIP $name : ShopDB has no driver name for it"
|
|
continue
|
|
}
|
|
if (-not (Get-PrinterDriver -Name $drivername -ErrorAction SilentlyContinue)) {
|
|
# Deliberately not fetched here - see the header.
|
|
Log "SKIP $name : driver '$drivername' is not staged on this PC"
|
|
continue
|
|
}
|
|
|
|
if ($WhatIfOnly) {
|
|
Log "WOULD create: $name -> $address ($drivername)"
|
|
continue
|
|
}
|
|
|
|
$portname = 'IP_' + $address
|
|
try {
|
|
if (-not (Get-PrinterPort -Name $portname -ErrorAction SilentlyContinue)) {
|
|
Add-PrinterPort -Name $portname -PrinterHostAddress $address -ErrorAction Stop
|
|
Log "port: $portname"
|
|
}
|
|
Add-Printer -Name $name -DriverName $drivername -PortName $portname -ErrorAction Stop
|
|
Log "created: $name -> $address ($drivername)"
|
|
} catch {
|
|
Log "ERROR creating ${name}: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
# The default is recorded, not applied: this process is SYSTEM and the setting
|
|
# is per user. Apply-ShopdbDefaultPrinter.ps1 reads it at logon.
|
|
if ($defaultname) {
|
|
if ($WhatIfOnly) {
|
|
Log "WOULD record default: $defaultname"
|
|
} else {
|
|
try {
|
|
if (-not (Test-Path $REGPATH)) { New-Item -Path $REGPATH -Force | Out-Null }
|
|
Set-ItemProperty -Path $REGPATH -Name DefaultPrinter -Value $defaultname
|
|
Log "default recorded for the logon task: $defaultname"
|
|
} catch {
|
|
Log "ERROR recording the default: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
} else {
|
|
Log 'no default assigned'
|
|
}
|
|
|
|
exit 0
|