# 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)" $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