Direkte Kommunikation mit Druckern unter Windows von Powershell aus


Manchmal ist es hilfreich, wenn man unter Windows direkt etwas ausdrucken kann. Direkt in diesem Fall bedeutet aber nicht über die üblichen Wege einen Druckertreiber anzusprechen, sondern, dass man direkt Steuerzeichen an einen Drucker senden kann. Man hat z. B. einen Druck in eine Datei umgeleitet und möchte nun diese Datei später an den Drucker senden. Dies ist mit den üblichen unter Powershell zur Verfügung stehenden Cmdlets wie Out-Printer nicht möglich.

Aber wie immer kann man das .Net Framework zu Hilfe nehmen. Grundlage für diesen Artikel ist der Knowledge Base Artikel https://support.microsoft.com/en-us/kb/322091. Dieser beschreibt die direkte Kommunikation mit Druckern unter C#. Hier hat sich nun jemand bereits die Mühe gemacht die Grundlage des Artikels an Powershell anzupassen: http://panchosoft.blogspot.de/2013/07/imprimir-en-impresora-de-etiquetas-sato.html. Das interessante an dieser Methode ist, dass es keine Rolle spielt, ob der Drucker per LPT- oder USB-Schnittstelle mit dem Rechner verbunden ist, noch ob mittels Netzwerk über TCP/IP oder WSD-Diensten mit ihm kommuniziert wird.

Die Daten finden mittels dieser Methode immer in Reinform ihren Weg zum Drucker, unabhängig vom Anschluss bzw. Übertragungsweg. Die generelle Druckvorgehensweise unter Windows ist hier beschrieben: https://msdn.microsoft.com/en-us/library/windows/desktop/dd145115%28v=vs.110%29.aspx.

Ein Aspekt ist noch wichtig. Es gibt sogenannte Typ 3 und Typ 4 Druckertreiber, dazu ein anderes Mal mehr. Typ 4 kommen verstärkt seit Windows 8 und nachfolgend zum Einsatz. Damit der Ansatz mit dem direkten senden von Daten an den Drucker klappt, muss dies berücksichtigt werden: https://msdn.microsoft.com/en-us/library/windows/desktop/ff686812(v=vs.110).aspx.

Hier zunächst das gesamte Script:

# Powershell RAW Print

$src = @‘
using System;
using System.Runtime.InteropServices;

public class RawPrinterHelper
{
// Structure and API declarions:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DOCINFOA
{
  [MarshalAs(UnmanagedType.LPStr)]
  public string pDocName;
  [MarshalAs(UnmanagedType.LPStr)]
  public string pOutputFile;
  [MarshalAs(UnmanagedType.LPStr)]
  public string pDataType;
}

  [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);

  [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool ClosePrinter(IntPtr hPrinter);

  [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);

  [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool EndDocPrinter(IntPtr hPrinter);

  [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool StartPagePrinter(IntPtr hPrinter);

  [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool EndPagePrinter(IntPtr hPrinter);

  [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);

}

‚@

Add-Type -TypeDefinition $src -Language CSharpVersion3

function Out-RawPrinter {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True, ParameterSetName="Printer", Position=0)]
        [PSTypeName(‚Microsoft.Management.Infrastructure.CimInstance#ROOT/StandardCimv2/MSFT_Printer‘)]
        [ciminstance]
        $Printer,
        [Parameter(Mandatory=$True, ParameterSetName="PrinterName", Position=0)]
        [String]$PrinterName,

        [String]$Output,
        [Byte[]]$ByteArray,
        [String]$DocTitle = "Powershell RAW print",
        [String]$DataType
    )

    If ($PSCmdlet.ParameterSetName -eq "PrinterName") {
        $Printer = Get-Printer $PrinterName
    }

    #
    If (((-Not ($Output)) -and (-Not ($ByteArray)))) {
        throw "Need some data, either by providing -Output or -ByteArray"
    }

    If ($Output) {
        $ByteArray = [System.Text.Encoding]::ASCII.GetBytes($Output)
        Write-Verbose "converted Output via ASCII-Encoding into ByteArray"
    }

    # DOCInfo Struktur erzeugen
    $di=New-Object RawPrinterHelper+DOCINFOA

    If (-Not ($DataType)) {
        # https://support.microsoft.com/en-us/kb/2779300 defines XPS_PASS
        If ((Get-PrinterDriver -Name $Printer.DriverName).MajorVersion -eq 4) {
            $di.pDataType = "XPS_PASS"
        } else {
            $di.pDataType = "RAW"
        }
    } else {
        $di.pDataType = $DataType
    }
    Write-Verbose "Type: $($di.pDataType)"
    $di.pDocName= $DocTitle

    # benötigte Variablen
    $hPrinter=[IntPtr]::Zero
    $dwWritten=0
    $result = $false

    If ([RawPrinterHelper]::OpenPrinter($Printer.Name.Normalize(), [ref] $hPrinter, [intptr]::Zero)) {
        If ([RawPrinterHelper]::StartDocPrinter($hPrinter, 1, $di)) {
            #$result = [RawPrinterHelper]::StartPagePrinter($hPrinter)
            $pa=[System.Runtime.InteropServices.GCHandle]::Alloc($ByteArray, [System.Runtime.InteropServices.GCHandleType]::Pinned)
            $result = [RawPrinterHelper]::WritePrinter($hPrinter, $pa.AddrOfPinnedObject(), $ByteArray.Length, [ref] $dwWritten);$Win32Error =  [ComponentModel.Win32Exception][System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
            # Win32Error, see http://www.exploit-monday.com/2016/01/properly-retrieving-win32-api-error.html
            Write-Verbose "$dwwritten Bytes sent to Printer ‚$($Printer.Name)‘, expected: $($ByteArray.Length)"
            $pa.Free()
            #$result = [RawPrinterHelper]::EndPagePrinter($hPrinter)
            $result = [RawPrinterHelper]::EndDocPrinter($hPrinter)
        }
        $result = [RawPrinterHelper]::ClosePrinter($hPrinter)
    }

}

Wie immer ist das Script nicht schön in dieser Darstellung aber es funktioniert per Copy&Paste.

Zunächst braucht man einen Druckernamen:

PS > Get-Printer| select Name

Name
—-
HP Officejet Pro X576 MFP PCL6
HP LaserJet A4/Letter PS Class Driver
HP Universal Printing PCL 6 (v6.3.0)
Microsoft XPS Document Writer
Microsoft Print to PDF
Fax
An OneNote 2013 senden

Nun wählt man am besten einen aus, in diesem Fall den ersten:

PS > $p=(get-printer)[0]
PS > $p

Name                           ComputerName    Type         DriverName
—-                           ————    —-         ———-
HP Officejet Pro X576 MFP PCL6                 Local        HP Officejet Pro X576

Nun kann man diesem Drucker Daten zukommen lassen:

$Schachttest ="$([char]27)&l4HSchacht1$([char]12)"+"$([char]27)&l1HSchacht2$([char]12)"

Out-RawPrinter –Printer $p –Output $Schachttest

Der etwas verkünstelte String druckt bei einem HP-Drucker zwei Seiten, eine vom Schacht1 (manueller) und eine vom Schacht2. Würde man in diesem Fall Out-Printer benutzen, dann würden die Steuerzeichen nicht interpretiert und als Text oder nichtdarstellbare Zeichen gedruckt.

Da die Umwandlung des Strings bei Out-RawPrinter mittels ASCII-Encoding stattfindet, stößt man auch schnell an eine Grenze, wenn man bestimmte Umlaute drucken möchte. Deshalb gibt es noch den Paramter –ByteArray anstatt –Output, somit kann man eine eigene Umwandlung durchführen und die Daten übergeben.

Dies kann man sich auch zunutze machen, wenn man einen Ausdruck in einer Datei umgelenkt hat und diese Datei dann später drucken möchte. Hier exemplarisch, wie man vorgeht:

$DruckDaten = Get-Content DruckDatei.PRN –Encoding Byte
Out-RawPrinter –Printer $p –ByteArray $DruckDaten

Neben diesen Möglichkeiten zur Kommunikation mit Druckern, möchte ich noch auf SNMP hinweisen, welches auch unter Windows 10 immer noch funktioniert, wie hier beschrieben: https://newyear2006.wordpress.com/2016/07/10/hp-netzwerkdrucker-per-remote-und-reboot-txt-neu-starten-nix-klappt-aber-snmp-bringt-die-lsung/.

2 Antworten to “Direkte Kommunikation mit Druckern unter Windows von Powershell aus”

  1. Direktes Drucken von PDF-Dateien unter Windows und wie man die Farbprofilunterstützung eines Druckers testen kann | Das nie endende Chaos! Says:

    […] man RAW-Dateien direkt an einen Drucker unter Windows senden kann, wurde bereits hier beschrieben: https://newyear2006.wordpress.com/2016/10/17/direkte-kommunikation-mit-druckern-unter-windows-von-po…. Diese Variante kann man nun ausbauen, um eben auch PDF-Dateien ohne weitere Zwischenbearbeitung […]

  2. Postscript direkt an Drucker unter Windows senden | Das nie endende Chaos! Says:

    […] Windows steht als nächstes dass direkte Drucken von Postscript auf dem Programm. Durch die hier https://newyear2006.wordpress.com/2016/10/17/direkte-kommunikation-mit-druckern-unter-windows-von-po… vorgestellte Methode um Druckern direkt Daten unterjubeln zu können und die hier […]

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s


%d Bloggern gefällt das: