Skip to content

Instantly share code, notes, and snippets.

@jonathanarbely
Last active July 29, 2026 14:01
Show Gist options
  • Select an option

  • Save jonathanarbely/f9ecbdd4f845bd4c9c9bdb3eea64034d to your computer and use it in GitHub Desktop.

Select an option

Save jonathanarbely/f9ecbdd4f845bd4c9c9bdb3eea64034d to your computer and use it in GitHub Desktop.
Bluetenshops Label Print Helper - Leine Apotheke (cannabis-leineapotheke.de) - Windows 10/11, DYMO LabelWriter 450 Turbo, 89x36mm Large Address

Label Helper - Leine Apotheke (Hannover)

Pre-configured install bundle for the operator computer at Leine Apotheke (cannabis-leineapotheke.de), the 10th shop. Drop-in: no edits required if the printer name matches what's on the machine.

What's configured

Setting Value
$AllowOrigin https://cannabis-leineapotheke.de
$PdfPrinterName LABOR_DYMO450 (CONFIRMED via Get-Printer 2026-07-29 - queue renamed by the pharmacy; DriverName is DYMO LabelWriter 450 Turbo, port USB001)
$ZplPrinterName (empty - no Zebra at this location)
$Port 9100
Windows version 10 / 11
PDF engine SumatraPDF (preferred) or Adobe Acrobat (fallback)
Label size 89 x 36 mm - DYMO "Large Address" (99012), same as Cantiva

Label stock: Large Address (89 x 36 mm)

Set server-side in the WP plugin (bluetenshops_product_label_generator_settings), nothing to configure in the helper. Changed 2026-07-29 from the inherited Charlottenburg 70 x 54 mm (DYMO 99015) to 89 x 36 mm with Cantiva's proven margins 3,3,2.5,3.

Render-verified after the change: the label fits on a single page with all content intact - including the Verschreibungspflichtig line, which is the first thing to clip when the layout overflows. Roughly 20% vertical headroom remains for longer strain names. Leine keeps its own (smaller) typography rather than Cantiva's, since the content templates differ.

Load DYMO 99012 Large Address stock in the printer.

Install (one command)

Have the operator paste this into a normal PowerShell prompt:

$b = "$env:TEMP\bs-bootstrap.ps1"
Invoke-WebRequest -Uri 'https://gist.githubusercontent.com/jonathanarbely/f9ecbdd4f845bd4c9c9bdb3eea64034d/raw/bootstrap.ps1' -OutFile $b
Unblock-File $b
powershell -ExecutionPolicy Bypass -File $b

The bootstrap will:

  1. Install SumatraPDF silently (GH mirror first, sumatrapdfreader.org fallback) - no winget dependency
  2. Stop any old helper on port 9100
  3. Install the helper into %LOCALAPPDATA%\BluetenshopsLabelHelper\label-print-helper.ps1
  4. Grant the localhost URL ACL (one UAC prompt, first run only)
  5. Create the hidden-window Startup shortcut (auto-start on every login)
  6. Start the helper and print /status

Expected final line:

ok zpl= pdf=LABOR_DYMO450 pdfReady=yes pdfEngine=sumatra pdfPrinterFound=yes

Notes

  • Use the Name column, never the model/driver. Leine's Get-Printer shows Name = LABOR_DYMO450, DriverName = DYMO LabelWriter 450 Turbo, PortName = USB001 - the pharmacy renamed the queue. SumatraPDF -print-to matches the NAME. Assuming the model name (as Waldshut's unit happens to use) would have failed here.
  • /status now reports pdfPrinterFound=yes|no - it checks the configured name against Get-Printer, so a wrong name shows up at install time rather than as a silent no-op at print time.
  • No Zebra at this location - the /print endpoint returns 500 no zpl printer configured if ever hit.
  • enable_split_dialog is false here (Charlottenburg-style), unlike the Angel Green shops - Drucken generates one label per order item directly.
  • No telemedicine provider yet - this shop is webshop-only, so labels are driven by the on-hold trigger status / the Drucken button.
  • See ../README.md for the full protocol reference and the Win 11 gotcha checklist.
# Bluetenshops Label Helper - single-step bootstrap for Leine Apotheke (Hannover).
#
# Does everything in one go (safe to re-run):
# 1. Installs SumatraPDF silently if not already present (no winget needed)
# 2. Stops any old helper instance
# 3. Downloads the latest label-print-helper.ps1 from the gist
# 4. Grants the localhost URL ACL (one UAC prompt, first run only)
# 5. Creates the hidden-window Startup shortcut
# 6. Starts the helper and prints /status
#
# Invocation (from a normal PowerShell prompt):
# $b = "$env:TEMP\bs-bootstrap.ps1"
# Invoke-WebRequest -Uri 'https://gist.githubusercontent.com/jonathanarbely/f9ecbdd4f845bd4c9c9bdb3eea64034d/raw/bootstrap.ps1' -OutFile $b
# Unblock-File $b
# powershell -ExecutionPolicy Bypass -File $b
$ErrorActionPreference = 'Stop'
$Port = 9100
$WorkDir = Join-Path $env:LOCALAPPDATA 'BluetenshopsLabelHelper'
$HelperPath = Join-Path $WorkDir 'label-print-helper.ps1'
$HelperUrl = 'https://gist.githubusercontent.com/jonathanarbely/f9ecbdd4f845bd4c9c9bdb3eea64034d/raw/label-print-helper.ps1'
# SumatraPDF installer. Primary: our public GH mirror (Chocolatey-bundled installer,
# bytewise-identical to upstream). Fallback: sumatrapdfreader.org direct. The mirror
# exists because sumatrapdfreader.org had an extended 502 outage during a deploy.
$SumatraUrls = @(
'https://github.com/jonathanarbely/label-helper-deps/releases/download/v1.0/SumatraPDF-3.6.1-64-install_x64.exe',
'https://www.sumatrapdfreader.org/dl/rel/3.5.2/SumatraPDF-3.5.2-64-install.exe'
)
Write-Host "===== Bluetenshops Label Helper bootstrap ====="
Write-Host ""
# 1) Ensure SumatraPDF is installed -----------------------------------------------------
$sumatraCandidates = @(
(Join-Path $env:LOCALAPPDATA 'SumatraPDF\SumatraPDF.exe'),
(Join-Path $env:LOCALAPPDATA 'Programs\SumatraPDF\SumatraPDF.exe'),
(Join-Path $env:ProgramFiles 'SumatraPDF\SumatraPDF.exe'),
(Join-Path ${env:ProgramFiles(x86)} 'SumatraPDF\SumatraPDF.exe')
)
$haveSumatra = $sumatraCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $haveSumatra) {
$g = Get-Command 'SumatraPDF.exe' -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
if ($g) { $haveSumatra = $g.Source }
}
if (-not $haveSumatra) {
Write-Host "[1/6] SumatraPDF not found - downloading installer..."
$installer = Join-Path $env:TEMP 'SumatraPDF-install.exe'
$downloaded = $false
foreach ($url in $SumatraUrls) {
Write-Host " trying $url"
try {
Invoke-WebRequest -Uri $url -OutFile $installer -UseBasicParsing -TimeoutSec 60
if ((Get-Item $installer).Length -gt 1000000) {
$downloaded = $true
Write-Host " downloaded $((Get-Item $installer).Length) bytes"
break
}
} catch {
Write-Warning " $url failed: $_"
}
}
if (-not $downloaded) {
Write-Warning "All SumatraPDF download URLs failed. Install SumatraPDF manually from https://www.sumatrapdfreader.org/ and re-run this bootstrap."
throw "SumatraPDF download failed"
}
Unblock-File $installer
# /S runs silent install. The Chocolatey-bundled installer is the official upstream
# binary and accepts /S the same way. If silent install needs elevation it cannot get,
# the installer returns non-zero - retry without /S so the user gets the GUI.
$p = Start-Process -FilePath $installer -ArgumentList '/S' -Wait -PassThru -NoNewWindow
if ($p.ExitCode -ne 0) {
Write-Warning "Silent install returned exit $($p.ExitCode); retrying without /S (per-user install)..."
Start-Process -FilePath $installer -Wait -NoNewWindow
}
Remove-Item $installer -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
$haveSumatra = $sumatraCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $haveSumatra) {
Write-Warning "SumatraPDF installation could not be verified. The helper will still install - please install SumatraPDF manually from https://www.sumatrapdfreader.org/ and then re-run this bootstrap."
} else {
Write-Host " installed to $haveSumatra"
}
} else {
Write-Host "[1/6] SumatraPDF already present at $haveSumatra"
}
# 2) Stop any running helper ------------------------------------------------------------
Write-Host "[2/6] Stopping any old helper on port $Port..."
$existing = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty OwningProcess -Unique
foreach ($procId in $existing) { Stop-Process -Id $procId -Force -ErrorAction SilentlyContinue }
Start-Sleep -Seconds 1
# 3) Install helper script --------------------------------------------------------------
Write-Host "[3/6] Installing helper to $WorkDir"
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
Invoke-WebRequest -Uri $HelperUrl -OutFile $HelperPath
Unblock-File $HelperPath
# 4) URL ACL (elevation required, first run only) ---------------------------------------
$urlAclCheck = (netsh http show urlacl url="http://localhost:$Port/") -join "`n"
if ($urlAclCheck -notmatch [regex]::Escape("$env:USERDOMAIN\$env:USERNAME")) {
Write-Host "[4/6] Granting URL ACL for http://localhost:$Port/ (one UAC prompt)..."
$aclScript = @"
netsh http add urlacl url=http://localhost:$Port/ user='$env:USERDOMAIN\$env:USERNAME'
netsh http add urlacl url=http://127.0.0.1:$Port/ user='$env:USERDOMAIN\$env:USERNAME'
"@
$aclTmp = Join-Path $env:TEMP 'bs-helper-acl.ps1'
$aclScript | Out-File -FilePath $aclTmp -Encoding utf8
Start-Process powershell -Verb RunAs -ArgumentList "-ExecutionPolicy Bypass -File `"$aclTmp`"" -Wait
Remove-Item $aclTmp -Force -ErrorAction SilentlyContinue
} else {
Write-Host "[4/6] URL ACL already granted; skipping."
}
# 5) Startup shortcut -------------------------------------------------------------------
$startup = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
New-Item -ItemType Directory -Path $startup -Force | Out-Null # some stripped Win11 user profiles lack this folder
$lnk = Join-Path $startup 'Mini-Label Helper.lnk'
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut($lnk)
$Shortcut.TargetPath = 'powershell.exe'
$Shortcut.Arguments = "-WindowStyle Hidden -ExecutionPolicy Bypass -File `"$HelperPath`""
$Shortcut.WorkingDirectory = $WorkDir
$Shortcut.Description = 'Bluetenshops label print helper'
$Shortcut.Save()
Write-Host "[5/6] Startup shortcut: $lnk"
# 6) Start helper and verify ------------------------------------------------------------
Start-Process powershell.exe -WindowStyle Hidden -ArgumentList '-ExecutionPolicy','Bypass','-File',$HelperPath
Start-Sleep -Seconds 3
try {
$r = Invoke-WebRequest -Uri "http://localhost:$Port/status" -UseBasicParsing -TimeoutSec 5
Write-Host ""
Write-Host "[6/6] ===== SUCCESS ====="
Write-Host $r.Content
Write-Host ""
Write-Host "Helper will auto-start on next login. Try printing from the shop admin now."
} catch {
Write-Warning "[6/6] Helper did not respond on /status. Check the deployed copy at $HelperPath and confirm the printer name matches Get-Printer."
throw
}
# Installs the Bluetenshops Mini-Label print helper on a Windows pharmacy
# computer with a USB-attached Zebra ZD410 (or compatible ZPL printer).
#
# What this does:
# 1. Copies label-print-helper.ps1 alongside this script into a stable
# location under %LOCALAPPDATA%\BluetenshopsLabelHelper.
# 2. Grants this user the URL ACL needed to bind localhost:9100 without
# admin (one-time, runs elevated).
# 3. Creates a hidden-window Startup shortcut so it auto-launches on login.
# 4. Starts the helper now and pings /status to confirm.
#
# Usage:
# 1. Edit label-print-helper.ps1 and change $AllowOrigin / $PrinterName
# if the shop or printer name differs from the default.
# 2. Right-click this file -> "Run with PowerShell" (or run from a normal
# PowerShell prompt - it self-elevates only the URL ACL step).
#
# Re-running is safe; existing files and shortcuts are overwritten.
$ErrorActionPreference = 'Stop'
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$Source = Join-Path $ScriptDir 'label-print-helper.ps1'
if (-not (Test-Path $Source)) {
throw "label-print-helper.ps1 not found next to this installer at $ScriptDir"
}
$InstallDir = Join-Path $env:LOCALAPPDATA 'BluetenshopsLabelHelper'
$Target = Join-Path $InstallDir 'label-print-helper.ps1'
Write-Host "Installing helper to $InstallDir"
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
Copy-Item -Path $Source -Destination $Target -Force
# 1) URL ACL (admin required; run once)
$acl = (netsh http show urlacl url=http://localhost:9100/) -join "`n"
if ($acl -notmatch [regex]::Escape("$env:USERDOMAIN\$env:USERNAME")) {
Write-Host "Granting URL ACL for http://localhost:9100/ (UAC prompt)..."
$aclScript = @"
netsh http add urlacl url=http://localhost:9100/ user='$env:USERDOMAIN\$env:USERNAME'
netsh http add urlacl url=http://127.0.0.1:9100/ user='$env:USERDOMAIN\$env:USERNAME'
"@
$tmp = Join-Path $env:TEMP 'bs-helper-acl.ps1'
$aclScript | Out-File -FilePath $tmp -Encoding utf8
Start-Process powershell -Verb RunAs -ArgumentList "-ExecutionPolicy Bypass -File `"$tmp`"" -Wait
} else {
Write-Host "URL ACL already granted; skipping."
}
# 2) Startup shortcut
$startup = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
New-Item -ItemType Directory -Path $startup -Force | Out-Null # some stripped Win11 user profiles lack this folder
$lnk = Join-Path $startup 'Mini-Label Helper.lnk'
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut($lnk)
$Shortcut.TargetPath = 'powershell.exe'
$Shortcut.Arguments = "-WindowStyle Hidden -ExecutionPolicy Bypass -File `"$Target`""
$Shortcut.WorkingDirectory = $InstallDir
$Shortcut.Description = 'Listens on localhost:9100, sends ZPL to local Zebra printer'
$Shortcut.Save()
Write-Host "Startup shortcut: $lnk"
# 3) Stop any old instance and start the new one
$existing = Get-NetTCPConnection -LocalPort 9100 -State Listen -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty OwningProcess -Unique
foreach ($p in $existing) { Stop-Process -Id $p -Force -ErrorAction SilentlyContinue }
Start-Sleep -Seconds 1
Start-Process powershell.exe -ArgumentList "-WindowStyle","Hidden","-ExecutionPolicy","Bypass","-File",$Target | Out-Null
Start-Sleep -Seconds 4
# 4) Verify
try {
$r = Invoke-WebRequest -Uri 'http://localhost:9100/status' -UseBasicParsing -TimeoutSec 5
Write-Host ""
Write-Host "SUCCESS: $($r.Content)"
Write-Host "Helper will auto-start on next login."
} catch {
Write-Warning "Helper did not respond on /status. Check the printer name in label-print-helper.ps1 matches an installed Windows printer (Get-Printer)."
throw
}
# Bluetenshops Label Print Helper - Leine Apotheke (Hannover)
#
# Pre-configured for:
# Shop: https://cannabis-leineapotheke.de
# PDF: DYMO LabelWriter 450 Turbo (single-roll). Product label is 70 x 54 mm
# (Charlottenburg-style stock, i.e. DYMO 99015) - set server-side in the
# WP plugin, nothing to configure here.
# ZPL: none (no Zebra at this location)
# Windows: 10/11
#
# Tiny HTTP listener on localhost that supports:
# POST /print-pdf - body = PDF -> silent print to the Dymo (product labels)
# POST /print - body = ZPL -> would route to a Zebra; unused here, kept for compatibility
# GET /status - health check
#
# Run:
# powershell -ExecutionPolicy Bypass -File label-print-helper.ps1
#
# Auto-start on login is handled by install-helper.ps1 (Startup shortcut).
$ErrorActionPreference = 'Stop'
$Port = 9100
$ZplPrinterName = '' # No Zebra at this location
# CONFIRMED via Get-Printer 2026-07-29. NOTE: this is the queue NAME, which the pharmacy
# renamed - the DriverName is "DYMO LabelWriter 450 Turbo" and the port is USB001.
# SumatraPDF -print-to matches the NAME column, never the driver/model.
$PdfPrinterName = 'LABOR_DYMO450'
$AllowOrigin = 'https://cannabis-leineapotheke.de'
# Locate a silent-print PDF engine. SumatraPDF (free, https://www.sumatrapdfreader.org/)
# is preferred - lightweight, native silent print. Adobe Acrobat / Reader is a fallback.
# This is called at startup AND on each /print-pdf request, so installing Sumatra
# while the helper is already running just works on the next print attempt.
function Find-PdfEngine {
$sumatraCandidates = @(
(Join-Path $env:LOCALAPPDATA 'SumatraPDF\SumatraPDF.exe'),
(Join-Path $env:LOCALAPPDATA 'Programs\SumatraPDF\SumatraPDF.exe'),
(Join-Path $env:LOCALAPPDATA 'Programs\SumatraPDF\SumatraPDF-3.exe'),
(Join-Path $env:ProgramFiles 'SumatraPDF\SumatraPDF.exe'),
(Join-Path ${env:ProgramFiles(x86)} 'SumatraPDF\SumatraPDF.exe')
)
foreach ($p in $sumatraCandidates) {
if ($p -and (Test-Path $p)) { return @{ Path = $p; Type = 'sumatra' } }
}
# PATH fallback - covers any non-standard install location
$cmd = Get-Command 'SumatraPDF.exe' -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
if ($cmd) { return @{ Path = $cmd.Source; Type = 'sumatra' } }
$acrobatCandidates = @(
'C:\Program Files\Adobe\Acrobat DC\Acrobat\Acrobat.exe',
'C:\Program Files (x86)\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe',
'C:\Program Files\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe'
)
foreach ($p in $acrobatCandidates) {
if (Test-Path $p) { return @{ Path = $p; Type = 'acrobat' } }
}
return $null
}
$PdfEngine = Find-PdfEngine
$AcrobatExe = if ($PdfEngine) { $PdfEngine.Path } else { $null } # legacy alias
# --- Raw printer P/Invoke (winspool.drv) ---------------------------------
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class RawPrinter {
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class DOCINFO {
[MarshalAs(UnmanagedType.LPWStr)] public string pDocName;
[MarshalAs(UnmanagedType.LPWStr)] public string pOutputFile;
[MarshalAs(UnmanagedType.LPWStr)] public string pDataType;
}
[DllImport("winspool.Drv", EntryPoint="OpenPrinterW", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern bool OpenPrinter(string p, out IntPtr h, IntPtr pd);
[DllImport("winspool.Drv", EntryPoint="ClosePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern bool ClosePrinter(IntPtr h);
[DllImport("winspool.Drv", EntryPoint="StartDocPrinterW", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern bool StartDocPrinter(IntPtr h, Int32 lvl, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFO di);
[DllImport("winspool.Drv", EntryPoint="EndDocPrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern bool EndDocPrinter(IntPtr h);
[DllImport("winspool.Drv", EntryPoint="StartPagePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern bool StartPagePrinter(IntPtr h);
[DllImport("winspool.Drv", EntryPoint="EndPagePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern bool EndPagePrinter(IntPtr h);
[DllImport("winspool.Drv", EntryPoint="WritePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern bool WritePrinter(IntPtr h, IntPtr buf, Int32 cnt, out Int32 written);
public static bool Send(string printer, byte[] bytes) {
IntPtr h;
var di = new DOCINFO { pDocName = "Mini Label", pDataType = "RAW" };
if (!OpenPrinter(printer, out h, IntPtr.Zero)) return false;
try {
if (!StartDocPrinter(h, 1, di)) return false;
try {
if (!StartPagePrinter(h)) return false;
IntPtr buf = Marshal.AllocCoTaskMem(bytes.Length);
try {
Marshal.Copy(bytes, 0, buf, bytes.Length);
Int32 written;
if (!WritePrinter(h, buf, bytes.Length, out written)) return false;
} finally { Marshal.FreeCoTaskMem(buf); }
EndPagePrinter(h);
} finally { EndDocPrinter(h); }
} finally { ClosePrinter(h); }
return true;
}
}
"@
# --- HTTP listener -------------------------------------------------------
$listener = New-Object System.Net.HttpListener
$listener.Prefixes.Add("http://localhost:$Port/")
$listener.Prefixes.Add("http://127.0.0.1:$Port/")
$listener.Start()
Write-Host "Label helper listening on http://localhost:$Port"
Write-Host " ZPL printer: $ZplPrinterName"
Write-Host " PDF printer: $PdfPrinterName (via $(if ($PdfEngine) { "$($PdfEngine.Type): $(Split-Path -Leaf $PdfEngine.Path)" } else { 'NO PDF ENGINE FOUND - install SumatraPDF or Acrobat Reader' }))"
function Set-Cors($resp) {
$resp.Headers.Add('Access-Control-Allow-Origin', $AllowOrigin)
$resp.Headers.Add('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
$resp.Headers.Add('Access-Control-Allow-Headers', 'Content-Type')
$resp.Headers.Add('Vary', 'Origin')
}
function Send-Text($resp, $code, $text) {
$resp.StatusCode = $code
$resp.ContentType = 'text/plain; charset=utf-8'
$bytes = [Text.Encoding]::UTF8.GetBytes($text)
$resp.ContentLength64 = $bytes.Length
$resp.OutputStream.Write($bytes, 0, $bytes.Length)
$resp.OutputStream.Close()
}
while ($listener.IsListening) {
try {
$ctx = $listener.GetContext()
$req = $ctx.Request
$resp = $ctx.Response
Set-Cors $resp
# CORS preflight
if ($req.HttpMethod -eq 'OPTIONS') {
$resp.StatusCode = 204
$resp.Close()
continue
}
# Health check - used by the dialog to detect the helper
if ($req.Url.AbsolutePath -eq '/status' -and $req.HttpMethod -eq 'GET') {
if (-not $PdfEngine) { $PdfEngine = Find-PdfEngine } # late-installed Sumatra/Acrobat picked up here
$pdfReady = if ($PdfEngine) { 'yes' } else { 'no' }
# Local must NOT case-collide with the script-scope $PdfEngine hashtable;
# PowerShell 5.1 treats $pdfEngine and $PdfEngine as the SAME variable, and
# the earlier `$pdfEngine = ...Type` would clobber $PdfEngine with a string,
# making the very next /print-pdf call hit Start-Process -FilePath $null.
# See bluetenshops/scripts/README.md "PS 5.1 case-insensitivity trap".
$pdfEngineLabel = if ($PdfEngine) { $PdfEngine.Type } else { 'none' }
# Does the configured printer actually exist on this machine? SumatraPDF
# -print-to needs an EXACT name match and fails QUIETLY when it does not,
# so surface the mismatch here at install time instead of at print time.
$printerFound = 'unknown'
try {
$names = @(Get-Printer -ErrorAction Stop | Select-Object -ExpandProperty Name)
$printerFound = if ($names -contains $PdfPrinterName) { 'yes' } else { 'NO' }
} catch { $printerFound = 'unknown' }
Send-Text $resp 200 "ok zpl=$ZplPrinterName pdf=$PdfPrinterName pdfReady=$pdfReady pdfEngine=$pdfEngineLabel pdfPrinterFound=$printerFound"
continue
}
# ZPL print endpoint (Zebra) - not used at Leine Apotheke, but kept for protocol compatibility
if ($req.Url.AbsolutePath -eq '/print' -and $req.HttpMethod -eq 'POST') {
$reader = New-Object IO.StreamReader $req.InputStream, ([Text.Encoding]::UTF8)
$zpl = $reader.ReadToEnd()
$reader.Close()
if (-not $zpl) { Send-Text $resp 400 'empty body'; continue }
# Override printer with ?printer=... if provided (e.g. ZD421 alongside ZD410)
$targetZplPrinter = $req.QueryString['printer']
if (-not $targetZplPrinter) { $targetZplPrinter = $ZplPrinterName }
if (-not $targetZplPrinter) { Send-Text $resp 500 'no zpl printer configured'; continue }
$bytes = [Text.Encoding]::ASCII.GetBytes($zpl)
$ok = [RawPrinter]::Send($targetZplPrinter, $bytes)
if ($ok) {
Write-Host ("[{0}] ZPL printed {1} bytes -> {2}" -f (Get-Date -Format HH:mm:ss), $bytes.Length, $targetZplPrinter)
Send-Text $resp 200 'printed'
} else {
Write-Host ("[{0}] ZPL FAILED - printer offline?" -f (Get-Date -Format HH:mm:ss))
Send-Text $resp 500 'print failed'
}
continue
}
# PDF print endpoint (Dymo)
if ($req.Url.AbsolutePath -eq '/print-pdf' -and $req.HttpMethod -eq 'POST') {
if (-not $PdfEngine) { $PdfEngine = Find-PdfEngine } # rescan in case Sumatra/Acrobat got installed after the helper started
if (-not $PdfEngine) { Send-Text $resp 500 'no pdf engine installed (install SumatraPDF or Acrobat Reader)'; continue }
# Override printer with ?printer=... if provided
$targetPrinter = $req.QueryString['printer']
if (-not $targetPrinter) { $targetPrinter = $PdfPrinterName }
# Read PDF bytes from request body to a temp file
$tmpPdf = Join-Path $env:TEMP ("bs-label-" + [Guid]::NewGuid().ToString() + ".pdf")
$fs = [IO.File]::Create($tmpPdf)
$req.InputStream.CopyTo($fs)
$fs.Close()
$size = (Get-Item $tmpPdf).Length
if ($size -lt 100) {
Remove-Item $tmpPdf -Force -ErrorAction SilentlyContinue
Send-Text $resp 400 'pdf too small'
continue
}
try {
if ($PdfEngine.Type -eq 'sumatra') {
# SumatraPDF: -print-to "Printer" -silent "file.pdf"
$argString = "-print-to `"$targetPrinter`" -silent `"$tmpPdf`""
} else {
# Acrobat: /n = new instance, /t = silent print to named printer, then close
$argString = "/n /t `"$tmpPdf`" `"$targetPrinter`""
}
$proc = Start-Process -FilePath $PdfEngine.Path `
-ArgumentList $argString `
-PassThru -WindowStyle Hidden
# Engine should exit on its own; give it up to 30 s
if (-not $proc.WaitForExit(30000)) {
$proc.Kill()
Write-Host ("[{0}] PDF TIMEOUT killed {1}" -f (Get-Date -Format HH:mm:ss), $PdfEngine.Type)
Send-Text $resp 500 "$($PdfEngine.Type) timeout"
continue
}
Write-Host ("[{0}] PDF printed {1} bytes -> {2} (via {3})" -f (Get-Date -Format HH:mm:ss), $size, $targetPrinter, $PdfEngine.Type)
Send-Text $resp 200 'printed'
} finally {
Remove-Item $tmpPdf -Force -ErrorAction SilentlyContinue
}
continue
}
Send-Text $resp 404 'not found'
} catch {
Write-Host "Error: $_"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment