HOWTO: Set Duplex Printing for Word Automation

Summary

Microsoft Word for Windows does not provide a method for Automation clients to set the duplex print flag before starting a print job. Although there is a parameter in the PrintOut method that indicates support for duplex printing, the parameter does not provide true duplex printing and may not be available depending on your operating system or installed language. However, developers can work around this limitation on Windows systems by changing the duplex flag for the active printer driver before calling Word's PrintOut function.


This article demonstrates how to use the Windows API to change the duplex setting of the active printer and allow a Word document to be printed in duplex.

More Information

This code uses the DocumentProperties API to change the print settings of the printer driver to enable duplex printing. For this code to work successfully, the end user will need adequate permissions to change the global print settings for the printer. If a user does not have the proper permission to change driver settings, they will get an Access Denied error on the OpenPrinter API call.

For users of Microsoft Windows NT and Microsoft Windows 2000 who need to print to a shared network printer, this can be a problem because the print driver does not reside on the local machine but on the print server. Although it is possible for an administrator to configure the print server to give end users the proper permission to change global settings, it is NOT desirable to do so in most cases. To work around this problem, it is possible to install a local print driver for the network printer, and let each user control the settings for their local systems.

Steps to Add Local Print Driver for Network Printer on Windows NT and Windows 2000

  1. One the Start menu, select Settings, then select Printers and double-click Add Printer to bring up the Add Printer wizard.
  2. When prompted, select printer from "My Computer" and NOT from the network. Although you will connect to a network printer, you want to use a driver on My Computer. Press Next to continue.
  3. Click on "Add Port."
  4. Select Local Port in the drop-down list box and then click New Port.
  5. Type the location of the printer on the network. For example:
        \\printserver\printername (using the exact path name to the printer)
  6. Select OK and continue with the rest of the setup.
Changing the printer properties for the active printer will affect all applications that use that printer, and not just Word. If you plan on changing the setting for a particular print job, make sure you restore the setting when the job is complete.

Steps to Build the Sample

  1. Open Visual Basic and create a new project. Form1 is created by default.
  2. Add a standard BAS module to the project and add the following code to the module's code window:
      
    Option Explicit

    Public Type PRINTER_DEFAULTS

    pDatatype As Long
    pDevmode As Long
    DesiredAccess As Long
    End Type

    Public Type PRINTER_INFO_2
    pServerName As Long
    pPrinterName As Long
    pShareName As Long
    pPortName As Long
    pDriverName As Long
    pComment As Long
    pLocation As Long
    pDevmode As Long ' Pointer to DEVMODE
    pSepFile As Long
    pPrintProcessor As Long
    pDatatype As Long
    pParameters As Long
    pSecurityDescriptor As Long ' Pointer to SECURITY_DESCRIPTOR
    Attributes As Long


    Priority As Long
    DefaultPriority As Long
    StartTime As Long
    UntilTime As Long
    Status As Long
    cJobs As Long
    AveragePPM As Long
    End Type

    Public Type DEVMODE
    dmDeviceName As String * 32

    dmSpecVersion As Integer
    dmDriverVersion As Integer
    dmSize As Integer
    dmDriverExtra As Integer
    dmFields As Long
    dmOrientation As Integer
    dmPaperSize As Integer
    dmPaperLength As Integer
    dmPaperWidth As Integer
    dmScale As Integer
    dmCopies As Integer
    dmDefaultSource As Integer
    dmPrintQuality As Integer
    dmColor As Integer
    dmDuplex As Integer
    dmYResolution As Integer
    dmTTOption As Integer
    dmCollate As Integer
    dmFormName As String * 32
    dmUnusedPadding As Integer
    dmBitsPerPel As Integer
    dmPelsWidth As Long
    dmPelsHeight As Long
    dmDisplayFlags As Long
    dmDisplayFrequency As Long
    dmICMMethod As Long
    dmICMIntent As Long
    dmMediaType As Long
    dmDitherType As Long
    dmReserved1 As Long
    dmReserved2 As Long
    End Type

    Public Const DM_DUPLEX = &H1000&
    Public Const DM_IN_BUFFER = 8

    Public Const DM_OUT_BUFFER = 2
    Public Const PRINTER_ACCESS_ADMINISTER = &H4
    Public Const PRINTER_ACCESS_USE = &H8
    Public Const STANDARD_RIGHTS_REQUIRED = &HF0000
    Public Const PRINTER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED Or _
    PRINTER_ACCESS_ADMINISTER Or PRINTER_ACCESS_USE)

    Public Declare Function ClosePrinter Lib "winspool.drv" _
    (ByVal hPrinter As Long) As Long
    Public Declare Function DocumentProperties Lib "winspool.drv" _
    Alias "DocumentPropertiesA" (ByVal hwnd As Long, _
    ByVal hPrinter As Long, ByVal pDeviceName As String, _
    ByVal pDevModeOutput As Long, ByVal pDevModeInput As Long, _
    ByVal fMode As Long) As Long
    Public Declare Function GetPrinter Lib "winspool.drv" Alias _
    "GetPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, _
    pPrinter As Byte, ByVal cbBuf As Long, pcbNeeded As Long) As Long
    Public Declare Function OpenPrinter Lib "winspool.drv" Alias _
    "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, _
    pDefault As PRINTER_DEFAULTS) As Long
    Public Declare Function SetPrinter Lib "winspool.drv" Alias _
    "SetPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, _
    pPrinter As Byte, ByVal Command As Long) As Long

    Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
    (pDest As Any, pSource As Any, ByVal cbLength As Long)

    ' ==================================================================
    ' SetPrinterDuplex
    '
    ' Programmatically set the Duplex flag for the specified printer
    ' driver's default properties.
    '
    ' Returns: True on success, False on error. (An error will also

    ' display a message box. This is done for informational value
    ' only. You should modify the code to support better error
    ' handling in your production application.)
    '
    ' Parameters:
    ' sPrinterName - The name of the printer to be used.
    '
    ' nDuplexSetting - One of the following standard settings:
    ' 1 = None
    ' 2 = Duplex on long edge (book)
    ' 3 = Duplex on short edge (legal)
    '
    ' ==================================================================
    Public Function SetPrinterDuplex(ByVal sPrinterName As String, _

    ByVal nDuplexSetting As Long) As Boolean

    Dim hPrinter As Long
    Dim pd As PRINTER_DEFAULTS
    Dim pinfo As PRINTER_INFO_2
    Dim dm As DEVMODE

    Dim yDevModeData() As Byte
    Dim yPInfoMemory() As Byte
    Dim nBytesNeeded As Long
    Dim nRet As Long, nJunk As Long

    On Error GoTo cleanup

    If (nDuplexSetting < 1) Or (nDuplexSetting > 3) Then
    MsgBox "Error: dwDuplexSetting is incorrect."
    Exit Function
    End If

    pd.DesiredAccess = PRINTER_ALL_ACCESS
    nRet = OpenPrinter(sPrinterName, hPrinter, pd)
    If (nRet = 0) Or (hPrinter = 0) Then
    If Err.LastDllError = 5 Then
    MsgBox "Access denied -- See the article for more info."
    Else
    MsgBox "Cannot open the printer specified " & _
    "(make sure the printer name is correct)."
    End If
    Exit Function
    End If

    nRet = DocumentProperties(0, hPrinter, sPrinterName, 0, 0, 0)
    If (nRet < 0) Then
    MsgBox "Cannot get the size of the DEVMODE structure."
    GoTo cleanup
    End If

    ReDim yDevModeData(nRet + 100) As Byte
    nRet = DocumentProperties(0, hPrinter, sPrinterName, _
    VarPtr(yDevModeData(0)), 0, DM_OUT_BUFFER)
    If (nRet < 0) Then
    MsgBox "Cannot get the DEVMODE structure."
    GoTo cleanup
    End If

    Call CopyMemory(dm, yDevModeData(0), Len(dm))

    If Not CBool(dm.dmFields And DM_DUPLEX) Then
    MsgBox "You cannot modify the duplex flag for this printer " & _
    "because it does not support duplex or the driver " & _
    "does not support setting it from the Windows API."
    GoTo cleanup
    End If

    dm.dmDuplex = nDuplexSetting
    Call CopyMemory(yDevModeData(0), dm, Len(dm))

    nRet = DocumentProperties(0, hPrinter, sPrinterName, _
    VarPtr(yDevModeData(0)), VarPtr(yDevModeData(0)), _
    DM_IN_BUFFER Or DM_OUT_BUFFER)

    If (nRet < 0) Then
    MsgBox "Unable to set duplex setting to this printer."
    GoTo cleanup
    End If

    Call GetPrinter(hPrinter, 2, 0, 0, nBytesNeeded)
    If (nBytesNeeded = 0) Then GoTo cleanup

    ReDim yPInfoMemory(nBytesNeeded + 100) As Byte

    nRet = GetPrinter(hPrinter, 2, yPInfoMemory(0), nBytesNeeded, nJunk)
    If (nRet = 0) Then
    MsgBox "Unable to get shared printer settings."
    GoTo cleanup
    End If

    Call CopyMemory(pinfo, yPInfoMemory(0), Len(pinfo))
    pinfo.pDevmode = VarPtr(yDevModeData(0))
    pinfo.pSecurityDescriptor = 0
    Call CopyMemory(yPInfoMemory(0), pinfo, Len(pinfo))

    nRet = SetPrinter(hPrinter, 2, yPInfoMemory(0), 0)
    If (nRet = 0) Then
    MsgBox "Unable to set shared printer settings."
    End If

    SetPrinterDuplex = CBool(nRet)

    cleanup:
    If (hPrinter <> 0) Then Call ClosePrinter(hPrinter)

    End Function


  3. On Form1, add a standard Command Button.
  4. Add the following code to the code window for Form1:
       Option Explicit

    Private Sub Command1_Click()
    Dim oWord As Object
    Dim oDoc As Object

    Set oWord = CreateObject("Word.application")

    oWord.Visible = True

    Set oDoc = oWord.Documents.Add
    oDoc.Range.Select


    oWord.Selection.TypeText "This is on page 1" & vbCr
    oWord.Selection.InsertBreak 1
    oWord.Selection.TypeText "This is page 2"

    SetPrinterDuplex Printer.DeviceName, 2

    oDoc.PrintOut Background:=False

    SetPrinterDuplex Printer.DeviceName, 1

    MsgBox "Print Done", vbMsgBoxSetForeground

    oDoc.Saved = True
    oDoc.Close
    Set oDoc = Nothing

    oWord.Quit
    Set oWord = Nothing
    End Sub

  5. Run the sample. If you have a printer that supports duplex printing, the test document should print on both sides of the page.

References

For more information on problems you might see while trying to print Word documents using a duplex printer, please see the following articles in the Microsoft Knowledge Base:

176189 WD97: Shading of Solid Black or Gray Won't Print Duplex on NT

196857 WD97: First Page Prints on Back of Last Page with Duplex

214683 WD97: Duplex Printing Does Not Duplex with Objects on Page
Propriétés

ID d'article : 230743 - Dernière mise à jour : 23 mars 2009 - Révision : 1

Commentaires