Skip to content

Platform Notes

Satyendra Singh edited this page Feb 10, 2026 · 1 revision

Platform Notes

Understanding platform-specific behavior helps you deploy @ssxv/node-printer successfully on Windows and Linux systems.

Windows Platform

Print Spooler Service

Windows Print Spooler manages all printing:

  • Service name: spooler (Windows Service)
  • Start type: Usually automatic
  • Dependencies: RPC services, Event Log

Common issues:

# Check spooler status
Get-Service spooler

# Restart spooler if needed
Restart-Service spooler

# View print queue
Get-WmiObject -Class Win32_PrintJob

Printer Drivers

Driver types on Windows:

  • Type 3: User-mode drivers (most common)
  • Type 4: Kernel-mode drivers (older)
  • Universal drivers: Windows 10+ preferred

Driver locations:

  • System drivers: %WINDIR%\System32\spool\drivers
  • Per-user drivers: %USERPROFILE%\AppData\Local\Microsoft\Windows\Printer

Windows Permissions

Required permissions for printing:

  • Print permission: Basic printing capability
  • Manage printers: Install/configure printers
  • Manage documents: Cancel other users' jobs

Service account considerations:

// When running as service account
const { printers } = require('@ssxv/node-printer');

try {
  const allPrinters = await printers.list();
  console.log('Available printers:', allPrinters.length);
} catch (error) {
  if (error.code === 'ACCESS_DENIED') {
    console.log('Service account lacks printer permissions');
    // Solution: Grant "Log on as a service" + printer permissions
  }
}

Windows User Sessions

Interactive vs service sessions:

  • Session 0: Services run here (no user printers)
  • Session 1+: User sessions (user-installed printers)

Headless Windows servers:

// Install printers system-wide, not per-user
// Use administrator account for printer installation
// Network printers work better than local printers

async function setupHeadlessPrinting() {
  const printers = await printers.list();
  
  // Filter for network printers (more reliable in headless environments)
  const networkPrinters = printers.filter(p => 
    p.type === 'network' || p.name.includes('\\\\')
  );
  
  if (networkPrinters.length === 0) {
    console.warn('No network printers found - consider adding network printers');
  }
  
  return networkPrinters;
}

Windows-Specific Features

Raw printing on Windows:

// Windows supports RAW datatype natively
const job = await jobs.printRaw({
  printer: 'Zebra Printer',
  data: data,
  type: 'RAW'  // Essential on Windows
});

Windows job priorities:

// Set job priority (Windows-specific)
const job = await jobs.printFile({ printer: 'HP LaserJet', file: 'document.pdf' });
await jobs.setNative('HP LaserJet', job.id, {
  priority: 'high'  // 'low', 'normal', 'high'
});

Linux Platform (CUPS)

CUPS Architecture

CUPS components:

  • cupsd: Main CUPS daemon
  • cupsd.conf: Main configuration file
  • PPD files: Printer descriptions
  • Filters: Data processing pipelines

CUPS directories:

  • Configuration: /etc/cups/
  • Spool directory: /var/spool/cups/
  • Log files: /var/log/cups/
  • PPD files: /etc/cups/ppd/

CUPS Configuration

Basic CUPS setup:

# Install CUPS (Ubuntu/Debian)
sudo apt-get install cups cups-client

# Start CUPS service
sudo systemctl start cups
sudo systemctl enable cups

# Add user to lpadmin group (printer management)
sudo usermod -a -G lpadmin $USER

# Check CUPS status
systemctl status cups

CUPS web interface:

  • URL: http://localhost:631
  • Requires admin user in lpadmin group
  • Useful for printer configuration

Linux Permissions

CUPS permission model:

# View current printer permissions
lpstat -p -d

# Grant user print permission
lpadmin -p PrinterName -u allow:username

# Grant group print permission  
lpadmin -p PrinterName -u allow:@groupname

File permissions for headless operation:

# CUPS runs as 'lp' user by default
# Ensure Node.js process can access CUPS socket
sudo usermod -a -G lp nodeuser

# Or run Node.js as lp user (less secure)
sudo -u lp node app.js

Linux Printer Types

Local printers:

// USB printers detected automatically
const localPrinters = await printers.list();
const usbPrinters = localPrinters.filter(p => 
  p.uri?.startsWith('usb://')
);

Network printers:

// Network printers (IPP, Socket, etc.)
const networkPrinters = await printers.list();
const ippPrinters = networkPrinters.filter(p =>
  p.uri?.startsWith('ipp://') || p.uri?.startsWith('socket://')
);

CUPS-Specific Features

PPD options:

// Get CUPS-specific printer options
const capabilities = await printers.capabilities('HP_LaserJet');
console.log(capabilities.options); // PPD-defined options

// Examples of PPD options:
// - PageSize: A4, Letter, Legal
// - Duplex: None, DuplexNoTumble, DuplexTumble  
// - ColorModel: Gray, RGB, CMYK

CUPS job attributes:

// Set CUPS-specific job options
const job = await jobs.printFile({ printer: 'HP_LaserJet', file: 'document.pdf' });
await jobs.setNative('HP_LaserJet', job.id, {
  'job-priority': 50,           // 1-100
  'job-hold-until': 'no-hold',  // CUPS hold options
  'media': 'iso_a4_210x297mm'   // CUPS media sizes
});

Cross-Platform Considerations

Printer Name Differences

Windows printer names:

  • Can contain spaces: "HP LaserJet Pro M404n"
  • May include server: "\\PRINTSERVER\HP_Laser"
  • Case-insensitive

Linux printer names:

  • Usually no spaces: HP_LaserJet_Pro_M404n
  • Case-sensitive
  • Follow CUPS naming conventions

Cross-platform printer discovery:

async function findPrinter(searchName) {
  const allPrinters = await printers.list();
  
  // Exact match first
  let printer = allPrinters.find(p => p.name === searchName);
  if (printer) return printer;
  
  // Case-insensitive match
  printer = allPrinters.find(p => 
    p.name.toLowerCase() === searchName.toLowerCase()
  );
  if (printer) return printer;
  
  // Partial match (contains search term)
  printer = allPrinters.find(p =>
    p.name.toLowerCase().includes(searchName.toLowerCase())
  );
  
  return printer;
}

Error Code Mapping

Standardized error codes across platforms:

const { jobs, PrinterError } = require('@ssxv/node-printer');

try {
  const job = await jobs.printFile({ printer: 'BadPrinter', file: 'document.pdf' });
} catch (error) {
  if (error instanceof PrinterError) {
    // Cross-platform error codes
    switch (error.code) {
      case 'PRINTER_NOT_FOUND':
        // Same code on Windows and Linux
        console.log('Printer does not exist');
        break;
      case 'ACCESS_DENIED':
        // Different underlying causes, same code
        console.log('Permission error');
        break;
      case 'PRINTER_OFFLINE':
        // Normalized across platforms
        console.log('Printer not available');
        break;
    }
    
    // Platform-specific details still available
    console.log('Platform details:', error.platformData);
  }
}

Headless and Server Deployments

Headless Windows Servers

Challenges:

  • No interactive desktop session
  • Limited printer driver support
  • Service account permissions

Solutions:

// Install printers system-wide
// Use network printers when possible
// Configure service to run as admin user

async function configureHeadlessWindows() {
  // Prefer network printers
  const printers = await printers.list();
  const networkPrinters = printers.filter(p => p.type === 'network');
  
  if (networkPrinters.length === 0) {
    console.warn('Consider adding network printers for headless operation');
  }
  
  return networkPrinters;
}

Windows service configuration:

<!-- App.config for Windows Service -->
<configuration>
  <appSettings>
    <add key="AllowInteractWithDesktop" value="false" />
    <add key="ServiceAccount" value="LocalSystem" />
  </appSettings>
</configuration>

Headless Linux Servers

Challenges:

  • No X11 display server
  • Minimal package installations
  • Container environments

Solutions:

# Minimal CUPS installation
sudo apt-get install --no-install-recommends cups cups-client

# Configure CUPS for headless operation
echo "WebInterface No" | sudo tee -a /etc/cups/cupsd.conf

# Enable network printer discovery
echo "Browsing On" | sudo tee -a /etc/cups/cupsd.conf
echo "BrowseLocalProtocols dnssd" | sudo tee -a /etc/cups/cupsd.conf

Docker considerations:

# Dockerfile for Node.js app with printing
FROM node:18-slim

# Install minimal CUPS
RUN apt-get update && apt-get install -y \
    libcups2-dev \
    cups-client \
    && rm -rf /var/lib/apt/lists/*

# Copy application
COPY . /app
WORKDIR /app

# Install dependencies and build
RUN npm install

# Run as non-root user
USER node
CMD ["node", "app.js"]

Container Deployments

Docker printing setup:

# docker-compose.yml
version: '3.8'
services:
  node-printer-app:
    build: .
    volumes:
      # Mount CUPS socket for printing access
      - /var/run/cups/cups.sock:/var/run/cups/cups.sock
      # Mount CUPS configuration (read-only)
      - /etc/cups:/etc/cups:ro
    environment:
      - CUPS_SERVER=unix:///var/run/cups/cups.sock

Kubernetes printing:

# kubernetes-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: printer-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: node-printer-app
        volumeMounts:
        - name: cups-socket
          mountPath: /var/run/cups/cups.sock
          readOnly: false
        - name: cups-config
          mountPath: /etc/cups
          readOnly: true
      volumes:
      - name: cups-socket
        hostPath:
          path: /var/run/cups/cups.sock
      - name: cups-config
        hostPath:
          path: /etc/cups

Network Printers

Windows Network Printing

UNC paths:

// Print to shared Windows printer
const job = await jobs.printFile({ printer: '\\\\PRINTSERVER\\HP_Laser', file: 'document.pdf' });

Network printer discovery:

async function findNetworkPrinters() {
  const allPrinters = await printers.list();
  return allPrinters.filter(p => 
    p.name.startsWith('\\\\') ||  // UNC path
    p.type === 'network'          // Network type
  );
}

Linux Network Printing

IPP printers:

// Print to IPP network printer
const job = await jobs.printFile({ printer: 'ipp-printer', file: 'document.pdf' });

// IPP printer URIs: ipp://host:631/printers/name

CUPS printer sharing:

# Enable printer sharing in CUPS
sudo cupsctl --share-printers
sudo cupsctl --remote-any

# Allow remote connections
echo "Listen *:631" | sudo tee -a /etc/cups/cupsd.conf

Troubleshooting

Windows Troubleshooting

Print spooler issues:

# Clear print queue
Stop-Service spooler
Remove-Item C:\Windows\System32\spool\PRINTERS\* -Force
Start-Service spooler

Driver problems:

# List installed printer drivers
Get-PrinterDriver | Select-Object Name, PrinterEnvironment

# Remove problematic driver
Remove-PrinterDriver -Name "HP LaserJet Driver"

Linux Troubleshooting

CUPS issues:

# Check CUPS error logs
sudo tail -f /var/log/cups/error_log

# Test printer connectivity
lpstat -p printer-name -l

# Clear CUPS job queue
cancel -a printer-name

Permission problems:

# Check CUPS socket permissions
ls -la /var/run/cups/cups.sock

# Add user to lp group
sudo usermod -a -G lp $USER

Generic Debugging

// Enable detailed logging
process.env.NODE_PRINTER_DEBUG = 'true';

const { printers, jobs } = require('@ssxv/node-printer');

async function debugPrinting() {
  try {
    console.log('Platform:', process.platform);
    
    // List all printers with details
    const allPrinters = await printers.list();
    console.log('Found printers:', JSON.stringify(allPrinters, null, 2));
    
    // Test default printer
    const defaultPrinter = await printers.default();
    console.log('Default printer:', defaultPrinter);
    
    // Test print job
    const job = await jobs.printFile({ printer: defaultPrinter.name, file: 'test.txt' });
    console.log('Test job submitted:', job.id);
    
    // Monitor job
    let attempts = 0;
    while (attempts < 10) {
      const status = await jobs.get(defaultPrinter.name, job.id);
      console.log(`Job status (${attempts}):`, status);
      
      if (status.state === 'completed' || status.state === 'error') {
        break;
      }
      
      await new Promise(resolve => setTimeout(resolve, 1000));
      attempts++;
    }
    
  } catch (error) {
    console.error('Debug error:', {
      message: error.message,
      code: error.code,
      platform: process.platform,
      stack: error.stack
    });
  }
}

debugPrinting();

Performance Considerations

Caching Printer Information

class PrinterCache {
  constructor(ttlMs = 30000) {  // 30 second cache
    this.cache = new Map();
    this.ttl = ttlMs;
  }
  
  async getPrinters() {
    const now = Date.now();
    const cached = this.cache.get('printers');
    
    if (cached && now - cached.timestamp < this.ttl) {
      return cached.data;
    }
    
    const printers = await printers.list();
    this.cache.set('printers', { data: printers, timestamp: now });
    return printers;
  }
}

const printerCache = new PrinterCache();

Batch Operations

// Batch job submission for better performance
async function submitBatchJobs(files, printer) {
  const jobPromises = files.map(file => 
    jobs.printFile({ printer, file }).catch(error => ({ error, file }))
  );
  
  return await Promise.all(jobPromises);
}

Security Considerations

Printer Access Control

// Validate printer access before printing
async function securePrint(filename, printerName, allowedPrinters) {
  if (!allowedPrinters.includes(printerName)) {
    throw new Error(`Printer '${printerName}' not in allowed list`);
  }
  
  // Additional security: verify file path
  if (!filename.startsWith('/safe/print/directory/')) {
    throw new Error('File not in allowed directory');
  }
  
  return await jobs.printFile({ printer: printerName, file: filename });
}

Audit Logging

// Log all print operations for security audit
const fs = require('fs').promises;

async function auditedPrint(filename, printer, userId) {
  const timestamp = new Date().toISOString();
  const logEntry = {
    timestamp,
    userId,
    action: 'print',
    filename: path.basename(filename),
    printer,
    fileSize: (await fs.stat(filename)).size
  };
  
  // Log the operation
  await fs.appendFile('/var/log/printing-audit.log', 
    JSON.stringify(logEntry) + '\n'
  );
  
  // Perform the print
  return await jobs.printFile({ printer, file: filename });
}

Next Steps