Skip to content

Instantly share code, notes, and snippets.

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

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

Select an option

Save jonathanarbely/8a849a21972cdef0cbb75afea972989f to your computer and use it in GitHub Desktop.
Bluetenshops Label Print Helper - Jestetten (Angel Green) - Windows 10/11, Zebra ZD410 + DYMO LabelWriter Wireless

Label Helper - Jestetten (Angel Green)

Pre-configured install bundle for the operator computer at the Jestetten location. Replaces the ad-hoc Downloads\label-print-helper.ps1 deployment with a proper install under %LOCALAPPDATA%\BluetenshopsLabelHelper\, gets the Startup shortcut so the helper relaunches on every login, and switches the PDF engine to SumatraPDF (silent print, no Acrobat required).

What's configured

Setting Value
$AllowOrigin https://jestetten.angelgreen.de
$ZplPrinterName Zebra ZD410
$PdfPrinterName DYMO LabelWriter Wireless
$Port 9100
Windows version 10 / 11
PDF engine SumatraPDF (preferred) or Adobe Acrobat (fallback)

Upgrading the existing Jestetten machine (one command)

The current helper at %USERPROFILE%\Downloads\label-print-helper.ps1 is the pre-SumatraPDF revision and has no Startup shortcut, so it dies on reboot.

Have the operator paste this into a normal PowerShell prompt:

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

The bootstrap will:

  1. Install SumatraPDF silently (direct download from sumatrapdfreader.org) if missing
  2. Stop any running helper on port 9100 - kills the old Downloads-resident process
  3. Install the latest 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
  6. Start the helper and print /status

Expected final line:

ok zpl=Zebra ZD410 pdf=DYMO LabelWriter Wireless pdfReady=yes pdfEngine=sumatra

After verifying labels still print correctly, the operator can delete the stale %USERPROFILE%\Downloads\label-print-helper.ps1 - the new install is independent.

Notes

  • Both printers active. Unlike Cannabis Charlottenburg (Dymo only), Jestetten has both a Zebra ZD410 (for mini labels via /print) and a Dymo LabelWriter Wireless (for product labels via /print-pdf).
  • Auto-start: the Startup shortcut at %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\Mini-Label Helper.lnk relaunches the helper at every login.
  • Idempotent: safe to re-run the bootstrap. Already-installed pieces are skipped.

See ../README.md for the full protocol reference and operations notes.

# Bluetenshops Label Helper - single-step bootstrap for Jestetten (Angel Green).
#
# 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 (incl. the pre-SumatraPDF one in %USERPROFILE%\Downloads)
# 3. Downloads the latest label-print-helper.ps1 from the Jestetten 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/8a849a21972cdef0cbb75afea972989f/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/8a849a21972cdef0cbb75afea972989f/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 - Jestetten (Angel Green)
#
# Pre-configured for:
# Shop: https://jestetten.angelgreen.de
# PDF: DYMO LabelWriter Wireless (product labels via /print-pdf)
# ZPL: Zebra ZD410 (mini labels via /print)
# Windows: 10/11
#
# Tiny HTTP listener on localhost that supports:
# POST /print - body = ZPL -> raw to the Zebra ZD410 (mini labels)
# POST /print-pdf - body = PDF -> silent print to the Dymo (product labels)
# 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 = 'Zebra ZD410' # ZPL printer for /print (verify with: Get-Printer)
$PdfPrinterName = 'DYMO LabelWriter Wireless' # PDF printer for /print-pdf (verify with: Get-Printer)
$AllowOrigin = 'https://jestetten.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 ZD410, mini labels)
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