# Set-ShopdbPrinters.ps1 # # Makes this PC's printers match what ShopDB says the bay should have. Asks # GET /api/printers/for-host/ 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