Skip to content

Instantly share code, notes, and snippets.

@jonathanarbely
Last active June 18, 2026 18:12
Show Gist options
  • Select an option

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

Select an option

Save jonathanarbely/91cb5baf8ef495e67c97846ef2cd792e to your computer and use it in GitHub Desktop.
Bluetenshops Label Print Helper - Angel Green Tiengen (angelgreen.de) - Windows 10/11, DYMO LabelWriter 450 Twin Turbo

Label Helper - Angel Green Tiengen

Pre-configured install bundle for the operator computer at Angel Green Tiengen (angelgreen.de). Drop-in: no edits required if the printer name matches what's on the machine.

What's configured

Setting Value
$AllowOrigin https://angelgreen.de
$PdfPrinterName DYMO LabelWriter 450 Twin Turbo
$ZplPrinterName (empty - no Zebra at this location)
$Port 9100
Windows version 10 / 11
PDF engine SumatraPDF (preferred) or Adobe Acrobat (fallback)
Label size same product-label size as Jestetten (set server-side in the WP plugin; nothing to configure here)

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/91cb5baf8ef495e67c97846ef2cd792e/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=DYMO LabelWriter 450 Twin Turbo pdfReady=yes pdfEngine=sumatra

Notes

  • Confirm the printer name first. Run Get-Printer | Select-Object Name and check the Dymo is exactly DYMO LabelWriter 450 Twin Turbo; if not, edit line 23 of label-print-helper.ps1 to match.
  • Twin Turbo rolls. The 450 Twin Turbo has two label rolls (left/right). Windows usually exposes it as one printer and prints to whichever roll is set active in the driver's printing preferences. If labels come out of the wrong side, set the correct roll as default in the Dymo driver preferences - the helper just prints to the named printer. (A specific roll can also be targeted later via the ?printer= override if Windows exposes two queues.)
  • No Zebra at this location - the /print endpoint returns 500 no zpl printer configured if ever hit; the mini-label dialog's helper-detection avoids triggering it.
  • See ../README.md for the full protocol reference and ../jestetten/ for the Zebra+Dymo variant.
# Bluetenshops Label Helper - single-step bootstrap for Angel Green Tiengen.
#
# 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/91cb5baf8ef495e67c97846ef2cd792e/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/91cb5baf8ef495e67c97846ef2cd792e/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 - Angel Green Tiengen
#
# Pre-configured for:
# Shop: https://angelgreen.de
# PDF: DYMO LabelWriter 450 Twin Turbo (same label size as Jestetten)
# 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
$PdfPrinterName = 'DYMO LabelWriter 450 Twin Turbo' # Verify exact name with: Get-Printer
$AllowOrigin = 'https://angelgreen.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' }
Send-Text $resp 200 "ok zpl=$ZplPrinterName pdf=$PdfPrinterName pdfReady=$pdfReady pdfEngine=$pdfEngineLabel"
continue
}
# ZPL print endpoint (Zebra) - not used at Angel Green Tiengen, 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