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/.