ZuloOne
English
Sign in
Menu

Print agents

Updated

The office print dialog has two destinations. One is the browser (window.print()). Print agents are the other — a small native host on Windows or Linux that signs into the tenant as a person, ticks the printers on that machine, and prints whatever the spooler can take.

This is platform infrastructure, not a workspace business model. Do not revive MIQS KERNEL.PRINTERS, PrintServer, PrintQueueTask, or Zyan IPrintService. Those tables are not on the tenant EF schema.

← Developer wiki

Why a separate process#

The tenant cannot see a USB label printer in a warehouse. The agent can. The split is the same as the Android app: the platform owns the queue and the ACL; the host is a native binary in zulo-print-agent. The agent does not load ERP documents, prices, or metadata. It hosts printers and prints blobs.

Why a person signs in (not a pairing code)#

The host of the machine is a user. They tick printers; those printers appear in the back office. Jobs still carry the office user who clicked Print, so the queue tells you who asked, not only which PC printed.

The agent uses POST /api/auth/login and refresh, the same tokens as the phone. There is no machine token.

Why the Windows service is not LocalSystem#

LocalSystem does not see the signed-in user's printers (network shares, USB drivers installed per-user). The tray installs the service to run as that Windows account (or a dedicated print user). If sc config cannot set the password, a logon scheduled task starts the same exe.

Tables (tenant EF, schema sync)#

Table Role
PrintAgents Machine, OS, last seen, signed-in host user, Online/Offline/Stopped
PrintAgentPrinters OS sysname, display name, Enabled (the checkbox), online
PrintJobs Printer, requested-by user, content type, blob, copies, state
RolePrinterAccess Role × printer, grant or revoke
UserPrinterAccess Extra ticks on the user card

An unchecked printer is disabled (and deleted only when no jobs remain). It does not vanish from history.

API#

All JWT.

  • POST /api/print/agents/register — upsert by machineId stored on disk
  • PUT /api/print/agents/me — heartbeat
  • PUT /api/print/agents/me/printers — the checkbox list
  • GET /api/print/agents/me/jobs?machineId=&waitSeconds= — long-poll, then GET …/jobs/{id}/content and PATCH Printing/Done/Error
  • GET /api/print/printers — catalog (Admin)
  • GET /api/print/printers/available — printers the current office user may use
  • POST /api/print/jobs — { printerId, copies } plus either { printFormMetaId, recordId } (server renders PDF via IDocumentPdfService) or { contentType, bytes } (Base64)
  • GET /api/print/jobs + PATCH Hold/Cancel/Queued + POST …/reprint — requires named permission print.queue.manage, not “anyone who can print”

ACL#

Enqueue requires access to that printer. A revoke on any of the user's own roles wins over grants and over extra ticks on the user. Superuser / Admin see every enabled printer. Agent pull requires the signed-in user to own that agent.

Script seam#

var queue = GetService<IPrintQueueService>();
await queue.EnqueuePrintFormAsync(printerId, printFormMetaId, documentId, copies: 1);
await queue.EnqueueFileAsync(printerId, "application/vnd.hp-pcl", bytes, copies: 1);

When a user is on the call, ACL still applies. A background job with no principal may enqueue onto any enabled printer. Word/Excel are not converted on the server; if the machine has a driver that accepts them, the raw file still goes through.

Trap#

MetaAgent is the job scheduler agent (enroll key, tags, run-as). Print agents are PrintAgent. Mixing the two in the UI or in a script will look like the printer catalog is empty while jobs still run on a warehouse box.

Do not assign HttpClient.BaseAddress after the first request — .NET throws "Properties can only be modified before sending the first request". Sign-in then register used the same client and hit that. Each call uses an absolute URI from the tenant name instead.

A dialog You must install .NET Desktop Runtime means the process is a framework-dependent apphost (dotnet build output, or an exe copied out of the folder). The installed SDK is not enough — the apphost wants Microsoft.WindowsDesktop.App at the exact patch the CI SDK used. The shipping zip is two self-contained single-file exes; they do not look for a machine runtime.

A Windows service set to this user without a password never starts (Cannot start service 'ZuloPrintAgent'). LocalSystem starts but does not see USB printers. Install service registers a logon scheduled task and starts zulo-print-agent.exe in the current session.

The Windows download is the zip, not the MSI. CI still builds an unsigned MSI, but Chrome marks that file as “not commonly downloaded”. Until the MSI is Authenticode-signed, only zulo-print-agent-win-x64.zip is offered. Unzip and run zulo-print-agent-tray.exe; Install service on the tray registers the Windows service.

The download links live in the customer cabinet (getzulo.com/cabinet → Apps), not on the marketing site. The agent asks for a tenant, a user and a password on its first screen, so a visitor who is not signed in cannot use the file — the install steps name their own stand instead of acme. The release assets themselves are the same zip CI publishes.

Same topic for other readers: User

Was this page helpful?