How to Set Printer Default Paper Bin in .NET

January 14, 2011

Yesterday I was challenged with a task that seemed trivial but it was a real pain to solve.

The customer had a VB.NET application that spawns Adobe Reader via command line options to force a specific PDF file to be printed on a specified printer (using the “semi-clandestine” /t switch). The problem that they were facing was that the VB.NET Compatibility Power Pack’s Printer object (used to simulate the equivalent object in VB6) didn’t change the default paper bin (or tray) for the printer. The change just sticks to the application’s session lifetime.

I turned to Win32 API for the solution. Indeed, there is a winspool.drv library that has the necessary functions to change several printer parameters. I used many blogs and forum posts for a source, which I list here:

Finally, the solution involved coaxing the Win32 API structures into the .NET world using marshalling (using System.Marshal namespace and its methods). I had no success until I set the CharSet attribute to Auto encoding for the API structures. It seems that by default it uses Ansi encoding and the data is messed up.

The final code for the tray change routine is here (it’s in VB.NET but easily translated into C#). It has four main methods:

  • SetTray (sets the default printer tray to a tray number. In VB.NET it’s the Printer.PaperBin or RawKind property)
  • GetPrinterName (gets the default printer name)
  • SavePrinterSettings (copies the printer settings into memory)
  • RestorePrinterSettings (replaces the printer settings with previously saved ones)
1. Imports System.Drawing.Printing
2. Imports System.Runtime.InteropServices

5. Public Class PrinterNative

8.     Private Declare Auto Function DocumentProperties Lib "winspool.drv" \_
9.      (ByVal hWnd As IntPtr, ByVal hPrinter As IntPtr, ByVal pDeviceName As String, \_
10.       ByVal pDevModeOutput As IntPtr, ByVal pDevModeInput As IntPtr, ByVal fMode As Int32) As Integer

12.     Public Declare Function GetPrinter Lib "winspool.drv" Alias "GetPrinterW" \_
13.      (ByVal hPrinter As IntPtr, ByVal Level As Integer, ByVal pPrinter As IntPtr, \_
14.       ByVal cbBuf As Integer, ByRef pcbNeeded As Integer) As Integer

16.     Private Declare Function SetPrinter Lib "winspool.drv" Alias "SetPrinterA" \_
17.      (ByVal hPrinter As IntPtr, ByVal level As Integer, ByVal pPrinterInfoIn As IntPtr, \_
18.       ByVal command As Int32) As Boolean

20.     <DllImport("winspool.drv", EntryPoint:="OpenPrinterA", ExactSpelling:=True, \_
21.        SetLastError:=True, CallingConvention:=CallingConvention.StdCall, \_
22.        CharSet:=CharSet.Ansi)> \_
23.     Private Shared Function OpenPrinter(ByVal pPrinterName As String, \_
24.   ByRef hPrinter As IntPtr, ByRef pDefault As PRINTER\_DEFAULTS) As Boolean
25.     End Function

27.     <DllImport("winspool.drv", EntryPoint:="ClosePrinter", SetLastError:=True, ExactSpelling:=True, \_
28.      CallingConvention:=CallingConvention.StdCall)> \_
29.     Private Shared Function ClosePrinter(ByVal hPrinter As Int32) As Boolean
30.     End Function

32.     Declare Function GetDefaultPrinter Lib "winspool.drv" Alias "GetDefaultPrinterA" \_
33.      (ByVal pszBuffer As System.Text.StringBuilder, ByRef pcchBuffer As Int32) As Boolean

35.     Declare Function SetDefaultPrinter Lib "winspool.drv" Alias "SetDefaultPrinterA" \_
36.      (ByVal pszPrinter As String) As Boolean

38.     Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" \_
39.      (ByVal hpvDest As IntPtr, ByVal hpvSource As IntPtr, ByVal cbCopy As Long)

42.     Private Structure PRINTER\_DEFAULTS
43.         Dim pDatatype As String
44.         Dim pDevMode As Long
45.         Dim pDesiredAccess As Long
46.     End Structure

48.     Private Const STANDARD\_RIGHTS\_REQUIRED = &HF0000
49.     Private Const PRINTER\_ACCESS\_ADMINISTER = &H4
50.     Private Const PRINTER\_ACCESS\_USE = &H8
51.     Private Const PRINTER\_ALL\_ACCESS = (STANDARD\_RIGHTS\_REQUIRED Or PRINTER\_ACCESS\_ADMINISTER Or PRINTER\_ACCESS\_USE)

53.     Private Const DM\_IN\_BUFFER As Integer = 8
54.     Private Const DM\_IN\_PROMPT As Integer = 4
55.     Private Const DM\_OUT\_BUFFER As Integer = 2

57.     Private Structure PRINTER\_INFO\_9
58.         Dim pDevMode As IntPtr
59.     End Structure

63.     <StructLayout(LayoutKind.Sequential)> \_
64.     Private Structure PRINTER\_INFO\_2
65.         <MarshalAs(UnmanagedType.LPTStr)> Public pServerName As String
66.         <MarshalAs(UnmanagedType.LPTStr)> Public pPrinterName As String
67.         <MarshalAs(UnmanagedType.LPTStr)> Public pShareName As String
68.         <MarshalAs(UnmanagedType.LPTStr)> Public pPortName As String
69.         <MarshalAs(UnmanagedType.LPTStr)> Public pDriverName As String
70.         <MarshalAs(UnmanagedType.LPTStr)> Public pComment As String
71.         <MarshalAs(UnmanagedType.LPTStr)> Public pLocation As String

73.         Public pDevMode As IntPtr

75.         <MarshalAs(UnmanagedType.LPTStr)> Public pSepFile As String
76.         <MarshalAs(UnmanagedType.LPTStr)> Public pPrintProcessor As String
77.         <MarshalAs(UnmanagedType.LPTStr)> Public pDatatype As String
78.         <MarshalAs(UnmanagedType.LPTStr)> Public pParameters As String

80.         Public pSecurityDescriptor As IntPtr
81.         Public Attributes As Integer
82.         Public Priority As Integer
83.         Public DefaultPriority As Integer
84.         Public StartTime As Integer
85.         Public UntilTime As Integer
86.         Public Status As Integer
87.         Public cJobs As Integer
88.         Public AveragePPM As Integer
89.     End Structure

92.     <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> \_
93.     Public Structure DEVMODE
94.         <MarshalAs(UnmanagedType.ByValTStr, Sizeconst:=32)> Public pDeviceName As String
95.         Public dmSpecVersion As Short
96.         Public dmDriverVersion As Short
97.         Public dmSize As Short
98.         Public dmDriverExtra As Short
99.         Public dmFields As Integer
100.         Public dmOrientation As Short
101.         Public dmPaperSize As Short
102.         Public dmPaperLength As Short
103.         Public dmPaperWidth As Short
104.         Public dmScale As Short
105.         Public dmCopies As Short
106.         Public dmDefaultSource As Short
107.         Public dmPrintQuality As Short
108.         Public dmColor As Short
109.         Public dmDuplex As Short
110.         Public dmYResolution As Short
111.         Public dmTTOption As Short
112.         Public dmCollate As Short
113.         <MarshalAs(UnmanagedType.ByValTStr, Sizeconst:=32)> Public dmFormName As String
114.         Public dmUnusedPadding As Short
115.         Public dmBitsPerPel As Integer
116.         Public dmPelsWidth As Integer
117.         Public dmPelsHeight As Integer
118.         Public dmNup As Integer
119.         Public dmDisplayFrequency As Integer
120.         Public dmICMMethod As Integer
121.         Public dmICMIntent As Integer
122.         Public dmMediaType As Integer
123.         Public dmDitherType As Integer
124.         Public dmReserved1 As Integer
125.         Public dmReserved2 As Integer
126.         Public dmPanningWidth As Integer
127.         Public dmPanningHeight As Integer

129.     End Structure

133.     Private pOriginalDEVMODE As IntPtr

136.     Public Sub SavePrinterSettings(ByVal printerName As String)
137.         Dim Needed As Integer
138.         Dim hPrinter As IntPtr
139.         If printerName = "" Then Exit Sub

141.         Try
142.             If OpenPrinter(printerName, hPrinter, Nothing) = False Then Exit Sub
143.             'Save original printer settings data (DEVMODE structure)
144.             Needed = DocumentProperties(Form1.Handle, hPrinter, printerName, Nothing, Nothing, 0)
145.             Dim pFullDevMode As IntPtr = Marshal.AllocHGlobal(Needed) 'buffer for DEVMODE structure
146.             DocumentProperties(Form1.Handle, hPrinter, printerName, pFullDevMode, Nothing, DM\_OUT\_BUFFER)
147.             pOriginalDEVMODE = Marshal.AllocHGlobal(Needed)
148.             CopyMemory(pOriginalDEVMODE, pFullDevMode, Needed)
149.         Catch ex As Exception
150.             MsgBox(ex.Message)
151.         End Try
152.     End Sub

154.     Public Sub RestorePrinterSettings(ByVal printerName As String)
155.         Dim hPrinter As IntPtr
156.         If printerName = "" Then Exit Sub

158.         Try
159.             If OpenPrinter(printerName, hPrinter, Nothing) = False Then Exit Sub
160.             Dim PI9 As New PRINTER\_INFO\_9
161.             PI9.pDevMode = pOriginalDEVMODE
162.             Dim pPI9 As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(PI9))
163.             Marshal.StructureToPtr(PI9, pPI9, True)
164.             SetPrinter(hPrinter, 9, pPI9, 0&)
165.             Marshal.FreeHGlobal(pPI9) 'pOriginalDEVMODE will be free too
166.             ClosePrinter(hPrinter)

169.         Catch ex As Exception
170.             MsgBox(ex.Message)
171.         End Try

174.     End Sub

176.     Function GetPrinterName() As String
177.         Dim buffer As New System.Text.StringBuilder(256)
178.         Dim PrinterName As String = String.Empty
179.         'Get default printer's name
180.         GetDefaultPrinter(buffer, 256)
181.         PrinterName = buffer.ToString
182.         If PrinterName = "" Then
183.             MsgBox("Can't find default printer.")
184.         End If
185.         Return PrinterName
186.     End Function

188.     Sub SetTray(ByVal printerName As String, ByVal trayNumber As Integer)
189.         Dim hPrinter As IntPtr
190.         Dim Needed As Integer

192.         OpenPrinter(printerName, hPrinter, Nothing)

194.         'Get original printer settings data (DEVMODE structure)
195.         Needed = DocumentProperties(IntPtr.Zero, hPrinter, printerName, Nothing, Nothing, 0)
196.         Dim pFullDevMode As IntPtr = Marshal.AllocHGlobal(Needed) 'buffer for DEVMODE structure
197.         DocumentProperties(IntPtr.Zero, hPrinter, printerName, pFullDevMode, Nothing, DM\_OUT\_BUFFER)

199.         Dim pDevMode9 As DEVMODE = Marshal.PtrToStructure(pFullDevMode, GetType(DEVMODE))

201.         ' Tray change
202.         pDevMode9.dmDefaultSource = trayNumber

204.         Marshal.StructureToPtr(pDevMode9, pFullDevMode, True)

206.         Dim PI9 As New PRINTER\_INFO\_9
207.         PI9.pDevMode = pFullDevMode

209.         Dim pPI9 As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(PI9))
210.         Marshal.StructureToPtr(PI9, pPI9, True)
211.         SetPrinter(hPrinter, 9, pPI9, 0&)
212.         Marshal.FreeHGlobal(pPI9) 'pFullDevMode will be free too

214.         ClosePrinter(hPrinter)
215.     End Sub

217. End Class

I hope it can be useful to somebody and spare him or her a day or two looking for an answer.


Profile picture

Written by Edin Kapić Insatiably curious code-writing tinkerer. Geek father. Aviation enthusiast. Cuisine journeyman. Follow me on Twitter