A queue was matched by NAME alone, so a bay whose printer had moved, or whose queue was built on a driver the site has since replaced, looked converged and printed to the wrong device. Absence was fixed; drift was not. Set-ShopdbPrinters.ps1 now repoints a queue whose port does not match the address ShopDB holds, and swaps a queue built on the wrong driver. Both are things ShopDB is authoritative about: where the printer IS, and what drives it. CORRECTED IN PLACE with Set-Printer, never removed and recreated. The queue keeps its name, its sharing, its permissions, and whoever has it as their default keeps it - which is what makes this safe to run every cycle on a live floor. There is still no removal code path in this script at all. Two guards, because a repair that breaks a working printer is worse than drift: the driver is only swapped when the wanted one is actually staged, and -WhatIfOnly reports both kinds of correction without making either. Verified on Windows against a queue that had the right name, the wrong port, the wrong driver AND was the logged-on user's default: both fields were corrected and the queue was still the default afterwards. The earlier no-op guarantees were re-run and still hold - nothing assigned changes nothing, and an assignment with no default leaves the user's own default alone.
227 lines
8.2 KiB
PowerShell
227 lines
8.2 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)"
|
|
|
|
function Ensure-Port([string]$address) {
|
|
$portname = 'IP_' + $address
|
|
if (-not (Get-PrinterPort -Name $portname -ErrorAction SilentlyContinue)) {
|
|
Add-PrinterPort -Name $portname -PrinterHostAddress $address -ErrorAction Stop
|
|
Log "port: $portname"
|
|
}
|
|
return $portname
|
|
}
|
|
|
|
function Repair-Queue($queue, [string]$address, [string]$drivername) {
|
|
$name = $queue.Name
|
|
|
|
# The address ShopDB holds is the truth about where the printer IS. A queue
|
|
# left pointing at the old address prints into the void, and looks fine.
|
|
if ($address) {
|
|
$wantedport = 'IP_' + $address
|
|
if ($queue.PortName -ne $wantedport) {
|
|
if ($WhatIfOnly) {
|
|
Log "WOULD repoint $name : $($queue.PortName) -> $wantedport"
|
|
} else {
|
|
try {
|
|
$portname = Ensure-Port $address
|
|
Set-Printer -Name $name -PortName $portname -ErrorAction Stop
|
|
Log "repointed $name : $($queue.PortName) -> $portname"
|
|
} catch {
|
|
Log "ERROR repointing ${name}: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# A queue built on a driver the site has moved off keeps using it forever.
|
|
# Only corrected when the wanted driver is actually staged - swapping a queue
|
|
# onto a driver that is not installed would break a working printer.
|
|
if ($drivername -and $queue.DriverName -ne $drivername) {
|
|
if (-not (Get-PrinterDriver -Name $drivername -ErrorAction SilentlyContinue)) {
|
|
Log "SKIP driver fix for $name : '$drivername' is not staged"
|
|
} elseif ($WhatIfOnly) {
|
|
Log "WOULD re-driver $name : $($queue.DriverName) -> $drivername"
|
|
} else {
|
|
try {
|
|
Set-Printer -Name $name -DriverName $drivername -ErrorAction Stop
|
|
Log "re-drivered $name : $($queue.DriverName) -> $drivername"
|
|
} catch {
|
|
Log "ERROR re-drivering ${name}: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($queue.PortName -eq ('IP_' + $address) -and
|
|
($drivername -eq '' -or $queue.DriverName -eq $drivername)) {
|
|
Log "present: $name"
|
|
}
|
|
}
|
|
|
|
$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 }
|
|
|
|
$address = $printer.hostname
|
|
if (-not $address) { $address = $printer.ipaddress }
|
|
$drivername = $printer.drivername
|
|
|
|
if ($existing.ContainsKey($name)) {
|
|
# A queue with the right NAME can still be wrong: pointing at a printer
|
|
# that has moved, or built on a driver that has since been replaced.
|
|
# Absence used to be the only thing fixed, so a bay with a stale queue
|
|
# looked converged and printed to the wrong device.
|
|
#
|
|
# Corrected IN PLACE with Set-Printer, never removed and recreated: the
|
|
# queue keeps its name, its sharing, its permissions, and whoever has it
|
|
# as their default keeps it.
|
|
Repair-Queue $existing[$name] $address $drivername
|
|
continue
|
|
}
|
|
|
|
if (-not $address) {
|
|
Log "SKIP $name : no hostname or IP to point a port at"
|
|
continue
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
try {
|
|
$portname = Ensure-Port $address
|
|
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
|