Skip to content

About

A .NET 10 service that prints Foundation tickets on demand over SignalR — CUPS on Raspberry Pi, the Windows spooler on a scale-house PC.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Web Print Service

A cross-platform .NET 10 print service that connects to a web application via SignalR and provides remote printing. Automatically detects the operating system and uses Windows Print (PowerShell/WMI) on Windows or CUPS (lpstat, lp) on Linux/macOS.

Architecture

Windows

Web App (BasicWeigh, etc.)
    |
    +-- SignalR Hub (/scaleHub)
            |
            +-- Web Print Service (this)
                    |
                    +-- SumatraPDF (silent PDF printing)
                            |
                            +-- Physical Printer (USB, Network, Shared)

Windows PDF Printing Requirements

The service needs a silent PDF printing tool to send tickets to printers. It auto-detects in this order:

  1. SumatraPDF (recommended) — deploy\install.ps1 downloads the portable SumatraPDF.exe into <install dir>\tools\ if none is already present
  2. PDFtoPrinter.exe — if placed in the service directory
  3. Fallback — uses Start-Process -Verb PrintTo (may open a dialog, and does nothing at all under a service account)

To provision it by hand — on a PC with no internet, or where a proxy blocked the installer's download — grab the 64-bit portable build from https://www.sumatrapdfreader.org/download-free-pdf-viewer and copy it to C:\Services\WebPrintService\tools\SumatraPDF.exe. No installer needed, and the next print picks it up.

winget is deliberately not used: it is absent on Server LTSC and on plenty of stock Windows builds, and its default per-user install lands where the LocalSystem service account cannot read it.

Orientation. SumatraPDF is invoked with -print-settings "disable-auto-rotation". Left to itself it rotates the page 90 degrees to match the printer's default paper orientation, which turns portrait tickets landscape on any PC whose printer defaults that way — while a PC using the shell fallback prints the same file portrait. Forcing the authored orientation makes every PC agree. Changing the printer's orientation in Windows does not fix it: this is content rotation, not paper orientation.

Linux / macOS / Raspberry Pi

Web App (BasicWeigh, etc.)
    |
    +-- SignalR Hub (/scaleHub)
            |
            +-- Web Print Service (this)
                    |
                    +-- CUPS (lpstat, lp, lpoptions)
                            |
                            +-- Physical Printer (USB, Network, IPP)

Features

  • Cross-Platform — automatically uses Windows Print or CUPS based on the OS
  • Windows Integration — uses PowerShell Get-Printer, Start-Process, Out-Printer to manage and print to local and network printers
  • CUPS Integration — uses lpstat, lp, lpoptions to manage and print to local printers on Linux/macOS
  • SignalR Connection — connects to any web app's SignalR hub (outbound only, works behind firewalls)
  • Printer Discovery — announces available CUPS or Windows printers to the web app on connect/reconnect
  • PDF Printing — downloads ticket PDFs from the web app and prints locally
  • Test Print — send a test page to any connected printer from the web UI
  • Swagger API — local REST API for configuration, testing, and diagnostics
  • SQLite Settings — persistent configuration stored locally (no appsettings.json edits needed)
  • Forever Retry — exponential backoff, never gives up reconnecting

Quick Start

Raspberry Pi install (recommended)

The installer provisions everything: CUPS, the bundled BIXOLON POS CUPS driver, a queue named TicketPrinter for the USB BIXOLON SRP-F310II ticket printer (override with --printer-name / --ppd), the .NET runtime, and a systemd service. The SignalR ServiceId defaults to the Pi's hostname — give each print box a unique hostname before installing.

git clone https://github.com/GTMichelli-Dev/web-print-service.git /tmp/wps
bash /tmp/wps/deploy/install.sh http://your-server:5110
rm -rf /tmp/wps

Re-run the same command to update; settings survive. See deploy/install.sh --help for all options (--printer-name, --service-id, --rotate, --media-size, ...). After install, assign <hostname>:TicketPrinter on Foundation's Setup → Printers page.

Windows install

Download web-print-service-win-x64.zip from Releases, unzip it on the PC the printer is attached to, and from an admin prompt in that folder:

INSTALL.bat https://your-server

Self-contained — no .NET, no SDK and no git needed on that PC. Full option set and the printing gotchas that bite on Windows are in Install on Windows.

Several sites

A printer several companies share serves every company's site. Give each site's URL. On Linux, separate them with spaces or commas; on Windows, put the list in quotes, separated by commas (unquoted, Windows splits it at each comma):

bash /tmp/wps/deploy/install.sh https://agnw.scaledata.net https://crs.scaledata.net
INSTALL.bat "https://agnw.scaledata.net,https://crs.scaledata.net"
powershell -ExecutionPolicy Bypass -File install.ps1 -WebUrl "https://agnw.scaledata.net,https://crs.scaledata.net"

They are kept as one comma-separated serverUrl. The service opens one connection per site, each with its own retries, so a site that is down does not affect the others. Every site sees the printers as its own and assigns them on its own Setup → Printers page. A print job fetches its PDF from the site that sent it.

Development

# Windows
cd WebPrintService
dotnet run

# Linux / Raspberry Pi
sudo apt-get install cups
dotnet run

Endpoints (Swagger at http://:/swagger)

Health & Status

GET /api/status/health

Returns service health, print system type, and printer count.

Response:

{
  "status": "ok",
  "printSystem": "Windows",
  "printSystemAvailable": true,
  "printerCount": 3,
  "printers": [
    { "printerId": "HP LaserJet", "displayName": "HP LaserJet", "status": "idle", "enabled": true, "isDefault": true }
  ]
}

GET /api/readme

Returns API documentation as JSON including all endpoints, parameters, response formats, and SignalR methods.


Printers

GET /api/printers

List all printers with their status.

Response:

[
  {
    "printerId": "HP_LaserJet_Pro",
    "displayName": "HP LaserJet Pro",
    "status": "idle",
    "isDefault": true,
    "enabled": true
  }
]

GET /api/printers/{printerId}/status

Get detailed status of a specific printer.

Example: GET /api/printers/HP_LaserJet_Pro/status

Response:

{
  "printerId": "HP_LaserJet_Pro",
  "status": "printer HP_LaserJet_Pro is idle. enabled since Mon 24 Mar 2026 08:00:00 AM CDT"
}

POST /api/printers/{printerId}/test

Send a test page to a printer. No request body needed.

Example: POST /api/printers/HP_LaserJet_Pro/test

Response:

{
  "success": true,
  "message": "Sent to HP_LaserJet_Pro"
}

Settings

GET /api/settings

Get current service settings.

Response:

{
  "id": 1,
  "serviceId": "default",
  "serverUrl": "http://localhost:5110",
  "signalRHub": "/scaleHub"
}

PUT /api/settings

Update service settings. Triggers a reconnect to each web app in serverUrl.

Request:

{
  "serviceId": "office-printer",
  "serverUrl": "http://192.168.1.100:5110"
}

Response:

{
  "success": true,
  "message": "Settings saved. Service restarting..."
}

SignalR Methods

Direction Method Description
Service -> Hub JoinPrintGroup(serviceId) Join the PrintClients group
Service -> Hub PrintServiceReady(announcement) Announce printers on connect
Service -> Hub PrinterListResponse(data) Respond to printer list request
Service -> Hub PrintResult(result) Report print job result
Service -> Hub TestPrintResult(result) Report test print result
Hub -> Service PrintTicket(data) Print a ticket PDF
Hub -> Service GetPrinterList Request printer list
Hub -> Service TestPrint(printerId) Send test page to printer
Hub -> Service ReloadConfig Restart the service

Platform Details

Windows

Uses PowerShell commands:

  • Get-Printer — enumerate all local and network printers
  • SumatraPDF -print-to <printer> -print-settings "disable-auto-rotation" -silent — print PDFs, staged into <install dir>\tools\ by deploy/install.ps1
  • Start-Process -Verb PrintTo — PDF fallback when no silent printer is present (wants a desktop, so it does nothing under a service account)
  • Out-Printer — print text files
  • rundll32 shimgvw.dll — print images

Linux / macOS (CUPS)

Uses CUPS command-line tools:

  • lpstat -p -d — enumerate printers and default
  • lp -d <printer> — print files
  • lpoptions -p <printer> — get printer options/description
  • CUPS web interface at https://localhost:631

Deployment

Install on Raspberry Pi / Linux

SSH into the Pi and run:

git clone https://github.com/GTMichelli-Dev/web-print-service.git /tmp/wps
bash /tmp/wps/deploy/install.sh https://basicscale.scaledata.net
rm -rf /tmp/wps

With options:

git clone https://github.com/GTMichelli-Dev/web-print-service.git /tmp/wps
bash /tmp/wps/deploy/install.sh https://basicscale.scaledata.net \
    --service-id office --port 5230
rm -rf /tmp/wps

For private repos, git will prompt for credentials. You can also use a deploy key or GitHub token.

Options:

Option Default Description
--service-id <id> default Unique ID for this service instance
--port <port> 5230 API/Swagger port
--branch <branch> master Git branch to install
--install-dir <path> /opt/web-print-service Install location

What the script does automatically:

  1. Detects Pi architecture (arm64, armv7l, x64)
  2. Installs CUPS (printer system)
  3. Installs the .NET 10 SDK and runtime permanently (skips download on future updates)
  4. Downloads latest source from GitHub
  5. Builds for the Pi's architecture
  6. Installs to /opt/web-print-service
  7. Preserves existing database on updates
  8. Registers and starts as a systemd service

Prerequisites: Just internet access and git (pre-installed on Raspberry Pi OS). No .NET, no CUPS — the script installs everything. The .NET SDK is installed permanently so future updates skip the download.

To update: Run the same command again. The script stops the service, updates files, preserves your database, and restarts.

After install:

  • Swagger: http://<pi-ip>:5230/swagger
  • CUPS Admin: http://<pi-ip>:631
  • Logs: sudo journalctl -u web-print-service -f
  • Restart: sudo systemctl restart web-print-service

Install on Windows

deploy/install.ps1 is the Windows counterpart to install.sh, with deploy/INSTALL.bat as a double-clickable wrapper so nobody has to touch the machine's execution policy.

Get the package. Every tagged release ships a prebuilt, self-contained web-print-service-win-x64.zip — the .NET runtime is bundled, so the target PC needs no .NET, no SDK and no git. Download it from Releases and unzip it on the PC the printer is attached to.

To build the same package by hand — publish into an app folder next to the scripts:

dotnet publish PiPrintService.csproj -c Release -r win-x64 --self-contained true -o C:\Temp\web-print\app
copy deploy\install.ps1          C:\Temp\web-print\
copy deploy\INSTALL.bat          C:\Temp\web-print\
copy deploy\package-README.txt   C:\Temp\web-print\README.txt

Install. From an admin prompt in that folder:

INSTALL.bat https://basicscale.scaledata.net

Or drive the script directly for the full option set:

powershell -ExecutionPolicy Bypass -File install.ps1 -WebUrl https://basicscale.scaledata.net -ServiceId scalehouse
Option Default Description
-WebUrl (required) Base URL of the web app. Must match its real scheme and port.
-ServiceId computer name Names this PC on the web app's Printers page.
-Port 5230 Local Swagger / diagnostic API port.
-InstallDir C:\Services\WebPrintService Install location.
-ServiceName WebPrintService Windows service name.
-TestPrinter (none) Print a test page to this printer at the end.
-ResetDb off Start from a clean database. Destroys the ServiceId, ServerUrl and printer settings — a timestamped backup is taken first regardless.
-SkipUrlCheck off Install even if the SignalR hub probe fails.
-SkipPdfTool off Don't download SumatraPDF when none is found.

The script is idempotent — re-run it to update. It will:

  1. Validate the arguments, find the binaries, and probe the web app's SignalR hub — a redirect or a 404 is reported before anything is installed, since either leaves the service reconnecting forever. (Negotiate is a POST; an http→https redirect downgrades it to GET and the hub answers 405 for good.)
  2. Stop the service and wait for it to actually stop (it holds its own .exe; copying too early fails with a file lock), plus stop any copy left running from a console.
  3. Back up the database to the Desktop, timestamped.
  4. Copy the binaries, excluding the database and its -wal/-shm companions.
  5. Write ServerUrl and the listen port into appsettings.json.
  6. Check that a silent PDF printer the service account can read is present, downloading the portable SumatraPDF.exe into <install dir>\tools\ if not — see Silent PDF printing.
  7. Create the service if missing — automatic startup, and configured to restart itself on failure (5s, 15s, then every 60s), since a scale-house PC is rarely watched. An existing service has its path corrected and startup set to automatic.
  8. Start it, poll /api/status/health until it answers, and list the printers the service can see — failing loudly rather than reporting success over a dead service.
  9. Apply ServiceId and ServerUrl through the API, then read them back.

Step 9 matters: appsettings.json only seeds the database on the first run, so on a machine that has been running for months, editing the config file alone would change nothing.

Manage it afterwards:

Get-Service WebPrintService
Restart-Service WebPrintService
Invoke-RestMethod http://localhost:5230/api/printers

Silent PDF printing

Tickets are PDFs, and printing one without a dialog needs a helper. The service looks for tools\SumatraPDF.exe in its install folder, then PDFtoPrinter.exe in the same folder, then the machine-wide C:\Program Files\... copies of either. Without one it falls back to Start-Process -Verb PrintTo, which wants a desktop — under a service account there isn't one, so tickets quietly never come out.

Step 5 downloads it, and does not use winget. The installer fetches the 64-bit portable SumatraPDF.exe over HTTPS straight into <install dir>\tools\. winget was tried first and is the wrong tool here twice over: plenty of scale-house PCs have no winget at all (Server LTSC, or any box without the Store's App Installer), and where it exists its default per-user scope drops the exe into the installing admin's profile — invisible to LocalSystem, whose LocalAppData is C:\Windows\System32\config\systemprofile\AppData\Local. That install then works perfectly when you double-click a PDF and not at all from the service. A portable exe next to the binaries has neither problem.

If the download is blocked, the installer warns and carries on. Fixing it later needs no reinstall: copy SumatraPDF.exe into <install dir>\tools\ (or drop PDFtoPrinter.exe into the install folder) and the next print finds it. Re-running the installer also does not wipe tools\ — the binary copy leaves it alone.

Printers follow the same rule. A printer added while logged in as yourself — including a shared \\server\printer — belongs to your profile and is invisible to LocalSystem. Either add it for all users, or point the service at that account in services.msc → WebPrintService → Log On. The installer prints the list of printers it can actually see; an empty list, or one missing the ticket printer, is this.

Run it in the foreground — for development, or to see an error the service swallows:

cd PiPrintService
dotnet run

Or, on a machine where it is already installed:

net stop WebPrintService
"C:\Services\WebPrintService\PiPrintService.exe"

Configuration

Initial config via appsettings.json:

{
  "Print": {
    "ServerUrl": "https://your-server",
    "Port": "5230"
  }
}

After first run, use the API to update settings (persists to SQLite):

curl -X PUT http://localhost:5230/api/settings \
  -H "Content-Type: application/json" \
  -d '{"serverUrl": "https://your-server", "serviceId": "office-printer"}'

serverUrl takes one URL or several, separated by commas, semicolons, spaces or new lines — see Several sites.

About

A .NET 10 service that prints Foundation tickets on demand over SignalR — CUPS on Raspberry Pi, the Windows spooler on a scale-house PC.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages