locked
Access Denied Error while changing network Printer Settings using "SetPrinter" from VB.NET

    Soru

  • Hello,
    I am developing a Word Add-in using VB.Net (VSTO 2010), where I would like to control some of the printer settings (such as duplex, print quality and orientation). I am trying to use the Winspool.drv's SetPrinter, but I get an "Access Denied" error when I call the SetPrinter procedure on a networked printer. The program works on my local machine where I am able to change the printer settings for a printer connected to my machine. However the Access Denied error occurs on the test machine, (Where the user has lower privileges - the user does not have admin privileges on the printer).
    Word 2010 is able to change duplex settings on the test machine, so I'm pretty sure I am not doing something right. I have tried with different access levels to no avail. I would appreciate it if someone could help me out here.
    I am using  VSTO 2010 on Word 2010 and Windows 7 64 bit for this add-in. Here is the code that I am using. I built this code following the example from this article http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/q140/2/85.asp&NoWebContent=1.

    The steps being followed here are:
    - Use OpenPrinter to get a handle to the printer
    - Call GetPrinter to get the size of the buffer required for the PRINTER_INFO_2 structure
    - Allocate the memory
    - Call GetPrinter again to fill in the buffer.
    - If the DEVMODE structure did not get filled in by GetPrinter, use DocumentProperties
      - Call DocumentProperties once to get the size of memory required for DEVMODE structure
      - Allocate the memory for the DEVMODE structure
      - Call DocumentProperties once again to fill in the DEVMODE structure
    - Make the duplex flag in the DEVMODE structure
    - Call DocumentProperties to merge the changes in the DEVMODE settings with the current settings.
    - Call SetPrinter to make the changes.
    - Close the printer handle
    - Release the memory allocated.
    Imports System.Runtime.InteropServices
    Imports System.Collections
    Imports System.ComponentModel
    
    Public Class ModifyPrinter
    #Region "Structures Definitions"
     <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
     Private Structure PRINTER_INFO_2
    
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pServerName As String
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pPrinterName As String
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pShareName As String
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pPortName As String
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pDriverName As String
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pComment As String
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pLocation As String
    
     Public pDevMode As IntPtr
    
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pSepFile As String
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pPrintProcessor As String
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pDatatype As String
     <MarshalAs(UnmanagedType.LPTStr)> _
     Public pParameters As String
    
     Public pSecurityDescriptor As IntPtr
     Public Attributes As Integer
     Public Priority As Integer
     Public DefaultPriority As Integer
     Public StartTime As Integer
     Public UntilTime As Integer
     Public Status As Integer
     Public cJobs As Integer
     Public AveragePPM As Integer
    
     End Structure
     <Flags()> _
     Enum DM As Integer
     Orientation = &H1
     PaperSize = &H2
     PaperLength = &H4
     PaperWidth = &H8
     Scale = &H10
     Position = &H20
     NUP = &H40
     DisplayOrientation = &H80
     Copies = &H100
     DefaultSource = &H200
     PrintQuality = &H400
     Color = &H800
     Duplex = &H1000
     YResolution = &H2000
     TTOption = &H4000
     Collate = &H8000
     FormName = &H10000
     LogPixels = &H20000
     BitsPerPixel = &H40000
     PelsWidth = &H80000
     PelsHeight = &H100000
     DisplayFlags = &H200000
     DisplayFrequency = &H400000
     ICMMethod = &H800000
     ICMIntent = &H1000000
     MediaType = &H2000000
     DitherType = &H4000000
     PanningWidth = &H8000000
     PanningHeight = &H10000000
     DisplayFixedOutput = &H20000000
     End Enum
     <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
     Public Structure DEVMODE
     Public Const CCHDEVICENAME As Integer = 32
     Public Const CCHFORMNAME As Integer = 32
    
     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCHDEVICENAME)> _
     Public dmDeviceName As String
     Public dmSpecVersion As Short
     Public dmDriverVersion As Short
     Public dmSize As Short
     Public dmDriverExtra As Short
     Public dmFields As DM
    
     Public dmOrientation As Short
     Public dmPaperSize As Short
     Public dmPaperLength As Short
     Public dmPaperWidth As Short
    
     Public dmScale As Short
     Public dmCopies As Short
     Public dmDefaultSource As Short
     Public dmPrintQuality As Short
     Public dmColor As Short
     Public dmDuplex As Short
     Public dmYResolution As Short
     Public dmTTOption As Short
     Public dmCollate As Short
     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=CCHFORMNAME)> _
     Public dmFormName As String
     Public dmLogPixels As Short
     Public dmBitsPerPel As Integer ' Declared wrong in the full framework
     Public dmPelsWidth As Integer
     Public dmPelsHeight As Integer
     Public dmDisplayFlags As Integer
     Public dmDisplayFrequency As Integer
    
     Public dmICMMethod As Integer
     Public dmICMIntent As Integer
     Public dmMediaType As Integer
     Public dmDitherType As Integer
     Public dmReserved1 As Integer
     Public dmReserved2 As Integer
     Public dmPanningWidth As Integer
     Public dmPanningHeight As Integer
    
     Public dmPositionX As Integer ' Using a PointL Struct does not work
     Public dmPositionY As Integer
     End Structure
     <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
     Public Structure PRINTER_DEFAULTS
     Public pDatatype As IntPtr
     Public pDevMode As IntPtr
     Public DesiredAccess As Integer
     End Structure
    
    #End Region
    #Region "Constants"
     Private Enum AccessMask
     READ_CONTROL = &H20000
     WRITE_DAC = &H40000
     PRINTER_ACCESS_USE = &H8
     PRINTER_ALL_ACCESS = 983052
     PRINTER_WRITE = 131080
     PRINTER_ACCESS_ADMINISTER = 4
     SERVER_EXECUTE = 131074
     SERVER_WRITE = 131075
     SERVER_ALL_ACCESS = 983043
    
     End Enum
    
     Private Const PRINTER_ACCESS_CODE = (AccessMask.READ_CONTROL Or AccessMask.PRINTER_ACCESS_USE)
     Private Const ERROR_INSUFFICIENT_BUFFER = 122
    
     Private Const DUPLEX_LONG_EDGE As Integer = 2
     Private Const DRAFT_RESOLUTION As Integer = 300
     Private Const DM_DUPLEX = &H1000&
    
     Private Enum DocProperties
     DM_UPDATE = 1
     DM_COPY = 2
     DM_PROMPT = 4
     DM_MODIFY = 8
    
     DM_IN_BUFFER = DM_MODIFY
     DM_IN_PROMPT = DM_PROMPT
     DM_OUT_BUFFER = DM_COPY
     DM_OUT_DEFAULT = DM_UPDATE
    
     End Enum
    #End Region
    #Region "DLL Import Statements"
     <DllImport("winspool.drv", _
      EntryPoint:="OpenPrinterA", _
      ExactSpelling:=True, _
      SetLastError:=True, _
      CallingConvention:=CallingConvention.StdCall, _
      CharSet:=CharSet.Ansi)> _
     Private Shared Function OpenPrinter(ByVal pPrinterName As String, _
         ByRef hPrinter As IntPtr, _
         ByRef pDefault As PRINTER_DEFAULTS) As Boolean
     End Function
    
     <DllImport("winspool.Drv", _
      EntryPoint:="GetPrinterA", _
      SetLastError:=True, _
      CharSet:=CharSet.Ansi, _
      ExactSpelling:=True, _
      CallingConvention:=CallingConvention.StdCall)> _
     Private Shared Function GetPrinter(ByVal hPrinter As IntPtr, _
         ByVal dwLevel As Int32, _
         ByVal pPrinter As IntPtr, _
         ByVal dwBuf As Int32, _
         ByRef dwNeeded As Int32) As Boolean
     End Function
     <DllImport("winspool.Drv", _
      EntryPoint:="DocumentPropertiesA", _
      SetLastError:=True, _
      ExactSpelling:=True, _
      CallingConvention:=CallingConvention.StdCall, _
      CharSet:=CharSet.Ansi)> _
     Private Shared Function DocumentProperties(ByVal hwnd As IntPtr, _
          ByVal hPrinter As IntPtr, _
          <MarshalAs(UnmanagedType.LPWStr)> ByVal pDeviceName As String, _
          ByVal pDevModeOutput As IntPtr, _
          ByVal pDevModeInput As IntPtr, _
          ByVal fMode As Integer) As Integer
     End Function
    
     <DllImport("winspool.Drv", _
      CharSet:=CharSet.Ansi, _
      EntryPoint:="ClosePrinter", _
      SetLastError:=True, _
      ExactSpelling:=True, _
      CallingConvention:=CallingConvention.StdCall)> _
     Private Shared Function ClosePrinter(ByVal hPrinter As IntPtr) As Boolean
     End Function
    
     <DllImport("winspool.Drv", _
      EntryPoint:="SetPrinterA", _
      SetLastError:=True, _
      CharSet:=CharSet.Ansi, _
      ExactSpelling:=True, _
      CallingConvention:=CallingConvention.StdCall)> _
     Private Shared Function SetPrinter(ByVal hPrinter As IntPtr, _
         ByVal Level As UInteger, _
         ByVal pPrinter As Integer, _
         ByVal Command As UInteger) As Boolean
     End Function
    #End Region
     ''' <summary>
     ''' Changes the duplex setting of the passed in printer using the Winspool.drv class.
     ''' </summary>
     ''' <param name="psPrinterName"></param>
     ''' <returns></returns>
     ''' <remarks></remarks>
     Public Function SetPrinterDuplex(ByVal psPrinterName As String) As Boolean
     Dim bStatus As Boolean = True
     Dim ptrPrinter, ptrPrinterInfo, ptrDevMode As System.IntPtr
     Dim stPrinterInfo As PRINTER_INFO_2
     Dim pd As New PRINTER_DEFAULTS()
     Dim iBytesNeeded, iLastError, iStatus As Integer
     Dim dm As DEVMODE
    
     ' Open a handle to the printer
     pd.pDatatype = 0
     pd.pDevMode = 0
     pd.DesiredAccess = PRINTER_ACCESS_CODE
     bStatus = OpenPrinter(psPrinterName, ptrPrinter, pd)
    
     If bStatus = False Then
      ' Close the printer and exit.
      iLastError = Marshal.GetLastWin32Error()
      MsgBox("Error while calling OpenPrinter")
      Throw New Win32Exception(iLastError)
     End If
    
     ' First call to GetPrinter to get memory size needed for PRINTER_INFO_2 structure. This call will
     ' return an insufficient buffer error. If GetPrinter fails for any other reason, then exit the process.
     bStatus = GetPrinter(ptrPrinter, 2, Nothing, 0, iBytesNeeded)
     iLastError = Marshal.GetLastWin32Error()
    
     If bStatus = False And iLastError <> ERROR_INSUFFICIENT_BUFFER Then
      ' Close printer and exit
      iLastError = Marshal.GetLastWin32Error()
      MsgBox("Error on first call to GetPrinter")
      ClosePrinter(ptrPrinter)
      Throw New Win32Exception(iLastError)
     End If
    
     ' Allocate the required amount of space
     ptrPrinterInfo = Marshal.AllocCoTaskMem(iBytesNeeded)
    
     ' Second call to GetPrinter to get all the current settings, so we can modify only the settings
     ' that we are interested in changing.
     bStatus = GetPrinter(ptrPrinter, 2, ptrPrinterInfo, iBytesNeeded, iBytesNeeded)
     If bStatus = False Then
    
      iLastError = Marshal.GetLastWin32Error()
      MsgBox("Error with 2nd GetPrinter call")
    
      ClosePrinter(ptrPrinter)
      Marshal.FreeCoTaskMem(ptrPrinterInfo)
    
      Throw New Win32Exception(iLastError)
     End If
    
    
     ' Convert the pointer to PRINTER_INFO_2 to the structure.
     stPrinterInfo = DirectCast(Marshal.PtrToStructure(ptrPrinterInfo, GetType(PRINTER_INFO_2)), PRINTER_INFO_2)
    
     ' If GetPrinter did not fill in the DEVMODE, then try to get it from DocumentProperties.
     If stPrinterInfo.pDevMode = IntPtr.Zero Then
      iBytesNeeded = 0
    
      ' Call Document Properties once to get the size of the buffer required to 
      iBytesNeeded = DocumentProperties(IntPtr.Zero, ptrPrinter, psPrinterName, IntPtr.Zero, IntPtr.Zero, 0)
    
      ' If the call failed, then close the printer, release the memory and throw an error
      If iBytesNeeded <= 0 Then
      iLastError = Marshal.GetLastWin32Error()
      ClosePrinter(ptrPrinter)
      Marshal.FreeCoTaskMem(ptrPrinterInfo)
      MsgBox("Error during 1st call to DocumentProperties")
      Throw New Win32Exception(iLastError)
      End If
    
      ' Allocate the memory 
      ptrDevMode = Marshal.AllocCoTaskMem(iBytesNeeded)
    
      ' Call DocumentProperties again to populate the DEVMODE properties
      iStatus = DocumentProperties(IntPtr.Zero, ptrPrinter, psPrinterName, ptrDevMode, IntPtr.Zero, DocProperties.DM_OUT_BUFFER)
      If iStatus <> 0 Then
      iLastError = Marshal.GetLastWin32Error()
      ClosePrinter(ptrPrinter)
      Marshal.FreeCoTaskMem(ptrPrinterInfo)
      Marshal.FreeCoTaskMem(ptrDevMode)
      MsgBox("Error during 2nd call to DocumentProperties")
      Throw New Win32Exception(iLastError)
      End If
    
      stPrinterInfo.pDevMode = ptrDevMode
      'Marshal.StructureToPtr(stPrinterInfo, ptrPrinterInfo, True)
    
     End If
    
     ' If the driver doesn't support the duplex change, then inform the user and exit.
    
     dm = DirectCast(Marshal.PtrToStructure(stPrinterInfo.pDevMode, GetType(DEVMODE)), DEVMODE)
     If Not Convert.ToBoolean(dm.dmFields And DM_DUPLEX) Then
      Return False
     End If
    
     ' Change the duplex flag in DEVMODE.
     dm.dmDuplex = DUPLEX_LONG_EDGE
    
     ' Call DocumentProperties once again to update the drive dependent part of DEVMODE.
     iStatus = DocumentProperties(IntPtr.Zero, ptrPrinter, psPrinterName, _
         stPrinterInfo.pDevMode, stPrinterInfo.pDevMode, _
         DocProperties.DM_IN_BUFFER Or DocProperties.DM_OUT_BUFFER)
     If iStatus = 0 Then
      iLastError = Marshal.GetLastWin32Error()
      MsgBox("Error calling Document Properties to update driver dependent parts of DEVMODE")
      ClosePrinter(ptrPrinter)
      Marshal.FreeCoTaskMem(ptrPrinterInfo)
      Marshal.FreeCoTaskMem(ptrDevMode)
      Throw New Win32Exception(iLastError)
     End If
    
     Marshal.StructureToPtr(stPrinterInfo, ptrPrinterInfo, False)
    
     ' Set the security descriptor to 0 since it's not being used.
     stPrinterInfo.pSecurityDescriptor = IntPtr.Zero
     bStatus = SetPrinter(ptrPrinter, 2, ptrPrinterInfo, 0)
    
     If bStatus = False Then
    
      iLastError = Marshal.GetLastWin32Error()
      MsgBox("Error calling SetPrinter")
      ClosePrinter(ptrPrinter)
      Marshal.FreeCoTaskMem(ptrPrinterInfo)
      Marshal.FreeCoTaskMem(ptrDevMode)
      Throw New Win32Exception(iLastError)
     Else
      ClosePrinter(ptrPrinter)
      Marshal.FreeCoTaskMem(ptrPrinterInfo)
      Marshal.FreeCoTaskMem(ptrDevMode)
      Return True
    
     End If
     End Function
    End Class
    
    

    Thanks
    Vikram


    • Taşıyan Andreas JohanssonModerator 22 Temmuz 2011 Cuma 14:12 Since using interop calls more suiable forum here (From:Common Language Runtime)
    22 Temmuz 2011 Cuma 13:02

Tüm Yanıtlar

  • Hello Vikram,

    Thanks for your post.

    Please check this article about changing Printer settings using Windows API which describes how to handle this similar error. Please have a try and let us know the situatin on your side.

    http://www.lessanvaezi.com/changing-printer-settings-using-the-windows-api/
    (Changing Printer settings using the Windows API)

    "In my testing, this worked fine on a local printer but when trying to change the settings on a network printer I was getting “access denied” errors. After some investigation, I got an explanation for what was happening. The following two articles provided the needed insight:

    If you ask for STANDARD_RIGHTS_REQUIRED, you may as well ask for the moon
    http://blogs.msdn.com/oldnewthing/archive/2008/02/27/7912126.aspx

    Changing printer settings using C# (also see one of the first comments in the thread)
    http://www.codeproject.com/KB/dotnet/NET_Printer_Library.aspx?display=PrintAll "

    If you have any concerns, please feel free to follow up.

    Have a nice day.

    Best regards


    Liliane Teng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    25 Temmuz 2011 Pazartesi 07:21
  • Hi Liliane,

    Thanks for your response. I went through the links that you provided and that still doesn't seem to resolve the problem. I cannot change the printer settings if I don't use PRINTER_ALL_ACCESS. Since asking for All Access is not a good idea (and not supported in an network environment), the only way for this work is to use the lower privileges (READ_CONTROL Or PRINTER_ACCESS_USE).

    One thing I discovered during my testing is that when I use VBA (the code listed in Lessan Vaezi's blog), I can use the lower privileges setting of PRINTER_NORMAL_ACCESS and change the settings on the network printer, but I cannot change it from VB.NET. I am completely baffled by this. What Is the difference between calling SetPrinter from VBA and VB.NET?

    Thanks
    Vikram

    25 Temmuz 2011 Pazartesi 15:52
  • 1)      Could you let me know the exact line of code?

    2)      Does the issue happen on 32-bit machines?


    --Trevor H.
    Send files to Hotmail.com: "MS_TREVORH"
    03 Ağustos 2011 Çarşamba 19:16
  •  

    1)      Could you let me know the exact line of code?

    2)      Does the issue happen on 32-bit machines?


    --Trevor H.
    Send files to Hotmail.com: "MS_TREVORH"

    1. The error occurs in line number 352 (bStatus = SetPrinter(ptrPrinter, 2, ptrPrinterInfo, 0)). 

    2. I haven't tried this on 32 bit machines, but I will run a test to find out and let you know.

     

    Thanks for your help.

    Vikram

    04 Ağustos 2011 Perşembe 12:30
  • Do you need more help?
    --Trevor H.
    Send files to Hotmail.com: "MS_TREVORH"
    09 Ağustos 2011 Salı 16:01
  • Yes, I am still not able to figure this out. I did try it out on a 32 bit machine and it does work on a local printer. I have to try this out on a network printer.

     

    Thanks for your help
    Vikram

    10 Ağustos 2011 Çarşamba 18:29
  • Please...

    1) Use PRINTER_INFO_9 instead of PRINTER_INFO_2.
    2) pd.DesiredAccess = PRINTER_ACCESS_USE

    ... and let us know.


    --Trevor H.
    Send files to Hotmail.com: "MS_TREVORH"
    10 Ağustos 2011 Çarşamba 20:53
  • Hi Trevor,

    Sorry for the delay in the reply, but I tried using PRINTER_INFO_9 and PRINTER_ACCESS_USE and it crashed Word. I am not sure if that is the answer. Here is the definition for PRINTER_INFO_9 that I used:

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
      Private Structure PRINTER_INFO_9
        Public pDevMode As IntPtr
      End Structure
    
    

    Thanks for your help.

    Vikram

    18 Ağustos 2011 Perşembe 08:44
  • Hello Vikram,

    could you solve your problem? I am facing exactly the same problems ... Using Printer_Info_2 leads to Access denied errors, using Printer_Info_9 crashes the windows explorer (further investigations have shown that after calling SetPrinter with Printer_Info_9 any call to OpenPrinter (e.g. from Word or Acrobat) hangs.)
    25 Ağustos 2011 Perşembe 08:47
  • This:
    For the PRINTER_INFO_2 and PRINTER_INFO_3 structures that contain a pointer to a security descriptor, the function can set only those components of the security descriptor that the caller has permission to modify. To set particular security descriptor components, you must specify the necessary access rights when you call the OpenPrinter or OpenPrinter2 function to retrieve a handle to the printer. The following table shows the access rights required to modify the various security descriptor components.
    Access permission Security descriptor component
    WRITE_OWNER
    Owner
    Primary group
    WRITE_DAC Discretionary access-control list (DACL)
    ACCESS_SYSTEM_SECURITY System access-control list (SACL)
    Means that you must open a handle with WRITE_DAC and WRITE_OWNER if you want to use PRINTER_INFO_2.
    For GetPrinter you will need READ_CONTROL and PRINTER_ACCESS_USE

    This lets me switch between long edge and top edge (1 and 2), I can't set 0 for some reason.
    Option Strict On
    Option Explicit On
    Option Infer Off
    
    Imports System.Runtime.InteropServices
    Imports System.Collections
    Imports System.ComponentModel
    Imports ConsoleApplication8.Consts
    
    Public Class ModifyPrinter
    
        Public Shared Sub Main()
            Dim mp As New ModifyPrinter
            mp.SetPrinterDuplex("HP LaserJet P2050 Series PCL6")
        End Sub
    
        <DllImport("winspool.Drv", CharSet:=CharSet.Ansi, SetLastError:=True)> _
        Private Shared Function OpenPrinter(ByVal pPrinterName As String, _
         ByRef hPrinter As IntPtr, _
         ByRef pDefault As PRINTER_DEFAULTS) As Boolean
        End Function
    
        <DllImport("winspool.Drv", CharSet:=CharSet.Ansi, SetLastError:=True)> _
        Private Shared Function GetPrinter(ByVal hPrinter As IntPtr, _
         ByVal dwLevel As Int32, _
         ByVal pPrinter As IntPtr, _
         ByVal dwBuf As Int32, _
         ByRef dwNeeded As Int32) As Boolean
        End Function
    
        <DllImport("winspool.Drv", CharSet:=CharSet.Ansi, SetLastError:=True)> _
        Private Shared Function DocumentProperties(ByVal hwnd As IntPtr, _
          ByVal hPrinter As IntPtr, _
          ByVal pDeviceName As String, _
          ByVal pDevModeOutput As IntPtr, _
          ByVal pDevModeInput As IntPtr, _
          ByVal fMode As Integer) As Integer
        End Function
    
        <DllImport("winspool.Drv", CharSet:=CharSet.Ansi, SetLastError:=True)> _
        Private Shared Function ClosePrinter(ByVal hPrinter As IntPtr) As Boolean
        End Function
    
        <DllImport("winspool.Drv", CharSet:=CharSet.Ansi, SetLastError:=True)> _
        Private Shared Function SetPrinter(ByVal hPrinter As IntPtr, _
         ByVal Level As UInteger, _
         ByVal pPrinter As IntPtr, _
         ByVal Command As UInteger) As Boolean
        End Function
    
        Friend Function SetPrinterDuplex(ByVal psPrinterName As String) As Boolean
            Dim ptrPrinter, ptrPrinterInfo, ptrDevMode As System.IntPtr
            Dim iBytesNeeded, iLastError, iStatus As Integer
            Dim pd As New PRINTER_DEFAULTS()
            ' So - this is different - we need READ_CONTROL, PRINTER_ACCESS_USE to read stuff.
            ' And we need PRINTER_ACCESS_ADMINISTER, WRITE_DAC, WRITE_OWNER to set stuff.
            ' Seems like overkill - I'd go with using PRINTER_INFO_9
            pd.DesiredAccess = AccessMask.READ_CONTROL Or AccessMask.PRINTER_ACCESS_USE Or AccessMask.PRINTER_ACCESS_ADMINISTER Or AccessMask.WRITE_DAC Or AccessMask.WRITE_OWNER
            Try
                Dim bStatus As Boolean = OpenPrinter(psPrinterName, ptrPrinter, pd)
                If bStatus = False Then Throw New Win32Exception()
                bStatus = GetPrinter(ptrPrinter, 2, Nothing, 0, iBytesNeeded)
                iLastError = Marshal.GetLastWin32Error()
                If bStatus = False AndAlso iLastError <> ERROR_INSUFFICIENT_BUFFER Then Throw New Win32Exception()
                ptrPrinterInfo = Marshal.AllocHGlobal(iBytesNeeded)
                bStatus = GetPrinter(ptrPrinter, 2, ptrPrinterInfo, iBytesNeeded, iBytesNeeded)
                If bStatus = False Then Throw New Win32Exception()
                Dim stPrinterInfo As PRINTER_INFO_2 = DirectCast(Marshal.PtrToStructure(ptrPrinterInfo, GetType(PRINTER_INFO_2)), PRINTER_INFO_2)
                Dim dm As DEVMODE = DirectCast(Marshal.PtrToStructure(stPrinterInfo.pDevMode, GetType(DEVMODE)), DEVMODE)
                If Not Convert.ToBoolean(dm.dmFields And DM_DUPLEX) Then
                    Return False
                End If
                Console.WriteLine("Duplex before: " & dm.dmDuplex) ' run again to see if it's changed
                dm.dmDuplex = 2 ' valid values  1 & 2
                Console.WriteLine("Trying to set to: " & dm.dmDuplex)
                ' jo0ls added this to copy the devmode back to memory:
                Marshal.StructureToPtr(dm, stPrinterInfo.pDevMode, True)
    
                iStatus = DocumentProperties(IntPtr.Zero, ptrPrinter, psPrinterName, _
                    stPrinterInfo.pDevMode, stPrinterInfo.pDevMode, _
                    DocProperties.DM_IN_BUFFER Or DocProperties.DM_OUT_BUFFER)
                If iStatus = 0 Then Throw New Win32Exception()
                Marshal.StructureToPtr(stPrinterInfo, ptrPrinterInfo, False)
                stPrinterInfo.pSecurityDescriptor = IntPtr.Zero
                bStatus = SetPrinter(ptrPrinter, 2, ptrPrinterInfo, 0)
                If bStatus = False Then Throw New Win32Exception
                Return True
            Finally
                If ptrPrinter <> IntPtr.Zero Then ClosePrinter(ptrPrinter)
                If ptrPrinterInfo <> IntPtr.Zero Then Marshal.FreeHGlobal(ptrPrinterInfo)
                If ptrDevMode <> IntPtr.Zero Then Marshal.FreeHGlobal(ptrDevMode)
            End Try
        End Function
    
    End Class
    

    The stuff that's not in that listing are in Vikram's post earlier. SetPrinter should have an IntPtr for pPrinter, so that's changed. Also the original code does not write the devmode structure back to memory after setting the desired duplex value - so there's a line required to do that.
    01 Ekim 2011 Cumartesi 21:36
  • For printer_info_9 you don't need as many rights.
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
        Friend Structure PRINTER_INFO_9
            Public pDevMode As IntPtr
        End Structure
    
        Friend Function SetPrinterDuplex(ByVal psPrinterName As String) As Boolean
            Dim ptrPrinter, ptrPrinterInfo, ptrDevMode As System.IntPtr
            Dim iBytesNeeded, iStatus As Integer
            Dim pd As New PRINTER_DEFAULTS
            pd.DesiredAccess = AccessMask.READ_CONTROL Or AccessMask.PRINTER_ACCESS_USE Or AccessMask.PRINTER_ACCESS_ADMINISTER
            Try
                Dim bStatus As Boolean = OpenPrinter(psPrinterName, ptrPrinter, pd)
                If bStatus = False Then Throw New Win32Exception()
                bStatus = GetPrinter(ptrPrinter, 9, Nothing, 0, iBytesNeeded)
                If bStatus = False AndAlso Marshal.GetLastWin32Error() <> ERROR_INSUFFICIENT_BUFFER Then Throw New Win32Exception()
                ptrPrinterInfo = Marshal.AllocHGlobal(iBytesNeeded)
                bStatus = GetPrinter(ptrPrinter, 9, ptrPrinterInfo, iBytesNeeded, iBytesNeeded)
                If bStatus = False Then Throw New Win32Exception()
                Dim stPrinterInfo As PRINTER_INFO_9 = DirectCast(Marshal.PtrToStructure(ptrPrinterInfo, GetType(PRINTER_INFO_9)), PRINTER_INFO_9)
                Dim dm As DEVMODE = DirectCast(Marshal.PtrToStructure(stPrinterInfo.pDevMode, GetType(DEVMODE)), DEVMODE)
                If Not Convert.ToBoolean(dm.dmFields And DM_DUPLEX) Then Return False
                Console.WriteLine("Duplex before: " & dm.dmDuplex) ' run again to see if it's changed
                dm.dmDuplex = 1 ' DMDUP_SIMPLEX = 1, DMDUP_VERTICAL = 2, DMDUP_HORIZONTAL = 3
                Console.WriteLine("Trying to set to: " & dm.dmDuplex)
                Marshal.StructureToPtr(dm, stPrinterInfo.pDevMode, False)
                iStatus = DocumentProperties(IntPtr.Zero, ptrPrinter, psPrinterName, _
                    stPrinterInfo.pDevMode, stPrinterInfo.pDevMode, _
                    DocProperties.DM_IN_BUFFER Or DocProperties.DM_OUT_BUFFER)
                If iStatus = 0 Then Throw New Win32Exception()
                bStatus = SetPrinter(ptrPrinter, 9, ptrPrinterInfo, 0)
                If bStatus = False Then Throw New Win32Exception
                Return True
            Finally
                If ptrPrinter <> IntPtr.Zero Then ClosePrinter(ptrPrinter)
                If ptrPrinterInfo <> IntPtr.Zero Then Marshal.FreeHGlobal(ptrPrinterInfo)
                If ptrDevMode <> IntPtr.Zero Then Marshal.FreeHGlobal(ptrDevMode)
            End Try
        End Function
    
    I didn't put the constants in the last post:
    Friend Enum AccessMask
        PRINTER_ACCESS_ADMINISTER = 4
        PRINTER_ACCESS_USE = 8
        READ_CONTROL = &H20000
        WRITE_DAC = &H40000
        WRITE_OWNER = &H80000
    End Enum
    
    01 Ekim 2011 Cumartesi 22:17
  • For network printers try

    ' SERVER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | SERVER_ACCESS_ADMINISTER | SERVER_ACCESS_ENUMERATE

    SERVER_ALL_ACCESS = &HF0003

    Duncan Jones

    24 Nisan 2012 Salı 08:59