Results 1 to 13 of 13

Thread: [RESOLVED] Printer status: detecting on or off

  1. #1

    Thread Starter
    PowerPoster Ellis Dee's Avatar
    Join Date
    Mar 2007
    Location
    New England
    Posts
    3,530

    Resolved [RESOLVED] Printer status: detecting on or off

    In my shutdown program -- which defrags the harddrive with Diskeeper Lite, scans for spyware with AdAware and then scans for viruses with AVG before powering down -- I want it to warn me if I left the printer on.

    Searching for existing threads uncovered this post, which links to this article. But that article is poorly constructed; it doesn't offer functional code.

    Can anyone help me piece it together? This is what I have so far:
    Code:
    Option Explicit
    
    Public Enum Printer_Status
        PRINTER_STATUS_READY = &H0
        PRINTER_STATUS_PAUSED = &H1
        PRINTER_STATUS_ERROR = &H2
        PRINTER_STATUS_PENDING_DELETION = &H4
        PRINTER_STATUS_PAPER_JAM = &H8
        PRINTER_STATUS_PAPER_OUT = &H10
        PRINTER_STATUS_MANUAL_FEED = &H20
        PRINTER_STATUS_PAPER_PROBLEM = &H40
        PRINTER_STATUS_OFFLINE = &H80
        PRINTER_STATUS_IO_ACTIVE = &H100
        PRINTER_STATUS_BUSY = &H200
        PRINTER_STATUS_PRINTING = &H400
        PRINTER_STATUS_OUTPUT_BIN_FULL = &H800
        PRINTER_STATUS_NOT_AVAILABLE = &H1000
        PRINTER_STATUS_WAITING = &H2000
        PRINTER_STATUS_PROCESSING = &H4000
        PRINTER_STATUS_INITIALIZING = &H8000
        PRINTER_STATUS_WARMING_UP = &H10000
        PRINTER_STATUS_TONER_LOW = &H20000
        PRINTER_STATUS_NO_TONER = &H40000
        PRINTER_STATUS_PAGE_PUNT = &H80000
        PRINTER_STATUS_USER_INTERVENTION = &H100000
        PRINTER_STATUS_OUT_OF_MEMORY = &H200000
        PRINTER_STATUS_DOOR_OPEN = &H400000
        PRINTER_STATUS_SERVER_UNKNOWN = &H800000
        PRINTER_STATUS_POWER_SAVE = &H1000000
    End Enum
    
    Private Type PRINTER_INFO_2
        pServerName As String
        pPrinterName As String
        pShareName As String
        pPortName As String
        pDriverName As String
        pComment As String
        pLocation As String
        pDevMode As Long
        pSepFile As String
        pPrintProcessor As String
        pDatatype As String
        pParameters As String
        pSecurityDescriptor As Long
        Attributes As Long
        Priority As Long
        DefaultPriority As Long
        StartTime As Long
        UntilTime As Long
        Status As Long
        JobsCount As Long
        AveragePPM As Long
    End Type
    
    Private Const CCHDEVICENAME = 32
    Private Const CCHFORMNAME = 32
    
    Private Type DEVMODE
        dmDeviceName As String * CCHDEVICENAME
        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 * CCHFORMNAME
        dmUnusedPadding As Integer
        dmBitsPerPel As Long
        dmPelsWidth As Long
        dmPelsHeight As Long
        dmDisplayFlags As Long
        dmDisplayFrequency As Long
    End Type
    
    Private Type PRINTER_DEFAULTS
        pDatatype As String
        pDevMode As DEVMODE
        DesiredAccess As Long
    End Type
    
    Private Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
    Private Declare Function GetPrinterApi Lib "winspool.drv" Alias "GetPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, buffer As Long, ByVal pbSize As Long, pbSizeNeeded As Long) As Long
    Private Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, pDefault As PRINTER_DEFAULTS) As Long
    
    
    Private Sub Command1_Click()
        Dim lret As Long
        Dim pDef As PRINTER_DEFAULTS
        Dim mhPrinter As Long
        Dim Index As Long
        
        lret = OpenPrinter(Printer.DeviceName, mhPrinter, pDef)
        
        Dim SizeNeeded As Long
        
        Dim buffer() As Long
        
        ReDim Preserve buffer(0 To 1) As Long
        lret = GetPrinterApi(mhPrinter, Index, buffer(0), UBound(buffer), SizeNeeded)
        ReDim Preserve buffer(0 To (SizeNeeded / 4) + 3) As Long
        lret = GetPrinterApi(mhPrinter, Index, buffer(0), UBound(buffer) * 4, SizeNeeded)
    
    Dim mPRINTER_INFO_2 As PRINTER_INFO_2
    With mPRINTER_INFO_2 '\\ This variable is of type PRINTER_INFO_2
       .pServerName = StringFromPointer(buffer(0), 1024)
       .pPrinterName = StringFromPointer(buffer(1), 1024)
       .pShareName = StringFromPointer(buffer(2), 1024)
       .pPortName = StringFromPointer(buffer(3), 1024)
       .pDriverName = StringFromPointer(buffer(4), 1024)
       .pComment = StringFromPointer(buffer(5), 1024)
       .pLocation = StringFromPointer(buffer(6), 1024)
       .pDevMode = buffer(7)
       .pSepFile = StringFromPointer(buffer(8), 1024)
       .pPrintProcessor = StringFromPointer(buffer(9), 1024)
       .pDatatype = StringFromPointer(buffer(10), 1024)
       .pParameters = StringFromPointer(buffer(11), 1024)
       .pSecurityDescriptor = buffer(12)
       .Attributes = buffer(13)
       .Priority = buffer(14)
       .DefaultPriority = buffer(15)
       .StartTime = buffer(16)
       .UntilTime = buffer(17)
       .Status = buffer(18)
       .JobsCount = buffer(19)
       .AveragePPM = buffer(20)
    End With
    ClosePrinter mhPrinter
    End Sub
    
    Public Function StringFromPointer(lpString As Long, lMaxLength As Long) As String
    
      Dim sRet As String
      Dim lret As Long
    
      If lpString = 0 Then
        StringFromPointer = ""
        Exit Function
      End If
    
      If IsBadStringPtrByLong(lpString, lMaxLength) Then
        '\\ An error has occured - do not attempt to use this pointer
          StringFromPointer = ""
        Exit Function
      End If
    
      '\\ Pre-initialise the return string...
      sRet = Space$(lMaxLength)
      CopyMemory ByVal sRet, ByVal lpString, ByVal Len(sRet)
      If Err.LastDllError = 0 Then
        If InStr(sRet, Chr$(0)) > 0 Then
          sRet = Left$(sRet, InStr(sRet, Chr$(0)) - 1)
        End If
      End If
    
      StringFromPointer = sRet
    
    End Function

  2. #2

    Thread Starter
    PowerPoster Ellis Dee's Avatar
    Join Date
    Mar 2007
    Location
    New England
    Posts
    3,530

    Re: Printer status: detecting on or off

    Drat, I was able to get the holes filled by using a post from here, but the code doesn't tell me whether the printer is on or off. Sure, there is an enumerated value for PRINTER_STATUS_OFFLINE, but it never returns that. When the printer is powered off, the printer status from the above code is set to PRINTER_STATUS_READY.

    So back to square one. Anyone know how to do this? All I want is to be able to throw up a msgbox saying "Turn off the printer, dummy!" before beginning the shutdown process, but only if the printer is actually on.

  3. #3

    Thread Starter
    PowerPoster Ellis Dee's Avatar
    Join Date
    Mar 2007
    Location
    New England
    Posts
    3,530

    Re: Printer status: detecting on or off

    Googling turned up much shorter code that actually works, which I have adapted into a generic utility function:
    Code:
    Public Function PrinterOffline(Optional pstrPrinter As String = "Default") As Boolean
        Dim strWhere As String
        Dim objWMI As Object
        Dim objPrinters As Object
        Dim objPrinter As Object
        
        Set objWMI = GetObject("winmgmts:\\.\root\CIMV2")
        If LCase$(pstrPrinter) = "default" Then
            strWhere = "Default = True"
        Else
            strWhere = "Name = '" & pstrPrinter & "'"
        End If
        Set objPrinters = objWMI.ExecQuery("SELECT * FROM Win32_Printer WHERE " & strWhere)
        For Each objPrinter In objPrinters
            PrinterOffline = objPrinter.WorkOffline
            Exit For
        Next
        Set objPrinter = Nothing
        Set objPrinters = Nothing
        Set objWMI = Nothing
    End Function
    Send this function the name of the printer you want to query, or send it nothing to query the default printer. Returns True if the printer is turned off or not ready, False if the printer is on.

    Does anyone know how I can remove the For...Each loop from the code? It strike me as quite inelegant.

  4. #4
    Super Moderator si_the_geek's Avatar
    Join Date
    Jul 2002
    Location
    Bristol, UK
    Posts
    41,974

    Re: Printer status: detecting on or off

    Presumably the For loop would be equivalent to this:
    Code:
            PrinterOffline = objPrinters(1).WorkOffline

  5. #5

    Thread Starter
    PowerPoster Ellis Dee's Avatar
    Join Date
    Mar 2007
    Location
    New England
    Posts
    3,530

    Re: Printer status: detecting on or off

    Nope, both of the following...

    objPrinters(0).WorkOffline
    objPrinters(1).WorkOffline

    ...generate "Generic Failure" errors, whatever the heck that means.

  6. #6

    Thread Starter
    PowerPoster Ellis Dee's Avatar
    Join Date
    Mar 2007
    Location
    New England
    Posts
    3,530

    Re: Printer status: detecting on or off

    If you run that code and add a breakpoint to Watch objPrinters, it shows the sub-object of objPrinters to be named "Item 1", with a space. What the heck?

    And of course, like virtually everything else I've tried...

    objPrinters.Item(1).WorkOffline

    ...raises a "Generic Failure" error.

  7. #7
    Super Moderator si_the_geek's Avatar
    Join Date
    Jul 2002
    Location
    Bristol, UK
    Posts
    41,974

    Re: Printer status: detecting on or off

    That is the normal way a collection is shown in the watch window.

    By a process of elimination I have found that objPrinters.Item(x) and just objPrinters(x) are both correct - but I can't get past the "Generic Failure" either, which is rather odd as the "For Each" does basically the same thing.

  8. #8
    VB6, XHTML & CSS hobbyist Merri's Avatar
    Join Date
    Oct 2002
    Location
    Finland
    Posts
    6,654

    Re: Printer status: detecting on or off

    For some reason I see nothing hard in this:
    Code:
    Public Function IsPrinterOff(Optional Printer As String) As Boolean
        Dim strWhere As String
        If LenB(Printer) Then
            strWhere = "Name = '" & Replace(Printer, "'", "\'") & "'"
        Else
            strWhere = "Default = TRUE"
        End If
        IsPrinterOff = GetObject("winmgmts:\\.\root\CIMV2").ExecQuery("SELECT * FROM Win32_Printer WHERE " & strWhere & " AND WorkOffline = TRUE").Count
    End Function
    You can even modify it to see if any printer is turned on. Just change the WQL.


    Code:
    Public Function PrintersOn() As Long
        Dim strWQL As String
        strWQL = "SELECT * FROM Win32_Printer WHERE WorkOffline = FALSE"
        PrintersOn = GetObject("winmgmts:\\.\root\CIMV2").ExecQuery(strWQL).Count
    End Function

  9. #9
    VB6, XHTML & CSS hobbyist Merri's Avatar
    Join Date
    Oct 2002
    Location
    Finland
    Posts
    6,654

    Re: Printer status: detecting on or off

    si_the_geek: enumeration (For Each) is provided by an entirely different mechanism than the regular Item interface. You may want to search and look at SelfEnum or ObjectCollection that I posted at some point, it shows you what is required to create the For Each mechanism "manually".

  10. #10
    Super Moderator si_the_geek's Avatar
    Join Date
    Jul 2002
    Location
    Bristol, UK
    Posts
    41,974

    Re: Printer status: detecting on or off

    That makes sense I guess.. thanks for easing my confusion!

  11. #11
    VB6, XHTML & CSS hobbyist Merri's Avatar
    Join Date
    Oct 2002
    Location
    Finland
    Posts
    6,654

    Re: Printer status: detecting on or off

    I also found this resource that should make it much easier to understand WMI: Windows 2000 Scripting Guide

    One of the interesting parts is how it tells you how to get a single object instead of getting all of them, by using Get instead of ExecQuery (I didn't try it yet, but I assume many of those are true for printers as well).

  12. #12
    VB6, XHTML & CSS hobbyist Merri's Avatar
    Join Date
    Oct 2002
    Location
    Finland
    Posts
    6,654

    Re: Printer status: detecting on or off

    Here is how you can get the status of the first printer installed in the system:
    Code:
    Public Function Test() As Boolean
        Test = GetObject("winmgmts:\\.\root\CIMV2").Get("Win32_Printer='" & Printers(0).DeviceName & "'").WorkOffline
    End Function
    
    Private Sub Form_Load()
        MsgBox Test
    End Sub
    Yeah, it makes use of VB's Printers collection


    Basically that Get would be the same you'd give to Item of the collection returned, but why get the collection if you can get single item this way as well... so you only want to use ExecQuery with For Each and nothing else.

  13. #13

    Thread Starter
    PowerPoster Ellis Dee's Avatar
    Join Date
    Mar 2007
    Location
    New England
    Posts
    3,530

    Re: Printer status: detecting on or off

    Thanks, Merri!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width