VBA to get toner from a selected printer on userform

malveiro

New Member
Joined
May 13, 2015
Messages
32
Hello

I have a vba userform with all the printers of my company (mono or colour printers) , and now i'm triyng to get the toner level ( only to display the remaining toner in percentage ) without no success
I've tried several approaches , i created a button Get toner , when i click , the 4 textboxes (if printer is colour ) created don't get populate with the information

The code i have is this

Private Sub GetToner_Click()
' Get the IP address from TextBox2
Dim ipAddress As String
ipAddress = TextBox2.Value

' Get the HTML content from the printer's web page
Dim htmlContent As String
htmlContent = GetHTMLContent("http://" & ipAddress)

' Extract toner levels from the HTML content
Dim tonerLevels() As String
tonerLevels = ExtractTonerLevels(htmlContent)

' Update the TextBoxes with toner levels
TextBox5.Value = tonerLevels(0) ' Cyan Toner Level
TextBox6.Value = tonerLevels(1) ' Magenta Toner Level
TextBox7.Value = tonerLevels(2) ' Yellow Toner Level
TextBox8.Value = tonerLevels(3) ' Black Toner Level
End Sub


Private Function ExtractTonerLevels(ByVal html As String) As String()
Dim doc As Object
Set doc = CreateObject("HTMLFile")
doc.body.innerHTML = html

Dim tonerLevels(0 To 3) As String

' Cyan Toner Level
Dim cyanElements As Object
Set cyanElements = doc.getElementsByTagName("b")
Dim i As Integer
For i = 0 To cyanElements.Length - 1
If cyanElements(i).innerText = "Cartucho ciano" Then
Dim cyanLevel As String
cyanLevel = cyanElements(i).ParentNode.NextSibling.getElementsByTagName("td")(0).getElementsByTagName("td")(0).Title
tonerLevels(0) = Mid(cyanLevel, 1, Len(cyanLevel) - 1)
Exit For
End If
Next i

' Magenta Toner Level
Dim magentaElements As Object
Set magentaElements = doc.getElementsByTagName("b")
For i = 0 To magentaElements.Length - 1
If magentaElements(i).innerText = "Cartucho magenta" Then
Dim magentaLevel As String
magentaLevel = magentaElements(i).ParentNode.NextSibling.getElementsByTagName("td")(0).getElementsByTagName("td")(0).Title
tonerLevels(1) = Mid(magentaLevel, 1, Len(magentaLevel) - 1)
Exit For
End If
Next i

' Yellow Toner Level
Dim yellowElements As Object
Set yellowElements = doc.getElementsByTagName("b")
For i = 0 To yellowElements.Length - 1
If yellowElements(i).innerText = "Cartucho Amarelo" Then
Dim yellowLevel As String
yellowLevel = yellowElements(i).ParentNode.NextSibling.getElementsByTagName("td")(0).getElementsByTagName("td")(0).Title
tonerLevels(2) = Mid(yellowLevel, 1, Len(yellowLevel) - 1)
Exit For
End If
Next i

' Black Toner Level
Dim blackElements As Object
Set blackElements = doc.getElementsByTagName("b")
For i = 0 To blackElements.Length - 1
If blackElements(i).innerText = "Cartucho Preto" Then
Dim blackLevel As String
blackLevel = blackElements(i).ParentNode.NextSibling.getElementsByTagName("td")(0).getElementsByTagName("td")(0).Title
tonerLevels(3) = Mid(blackLevel, 1, Len(blackLevel) - 1)
Exit For
End If
Next i

ExtractTonerLevels = tonerLevels
End Function


Function FindElementByTagNameAndAttribute(doc As Object, tagName As String, attributeName As String, attributeValue As String) As Object
' Find the first element in the document that matches the specified tag name and attribute
Dim elements As Object
Set elements = doc.getElementsByTagName(tagName)

Dim element As Object
For Each element In elements
If element.getAttribute(attributeName) = attributeValue Then
Set FindElementByTagNameAndAttribute = element
Exit Function
End If
Next element

' Return Nothing if no matching element is found
Set FindElementByTagNameAndAttribute = Nothing
End Function


Private Function GetElementByInnerText(ByVal elements As Object, ByVal innerText As String) As Object
Dim i As Integer
For i = 0 To elements.Length - 1
If elements(i).innerText = innerText Then
Set GetElementByInnerText = elements(i)
Exit Function
End If
Next i
Set GetElementByInnerText = Nothing
End Function

Private Function GetTonerLevelFromElement(ByVal element As Object) As String
Dim tableElement As Object
Set tableElement = element.parentElement.parentElement.parentElement.getElementsByTagName("table")(1)
If Not tableElement Is Nothing Then
GetTonerLevelFromElement = Mid(tableElement.Rows(1).Cells(0).Title, 1, Len(tableElement.Rows(1).Cells(0).Title) - 1)
Else
GetTonerLevelFromElement = ""
End If
End Function





Private Function GetRegExpMatch(ByVal inputString As String, ByVal pattern As String) As Object
Dim regex As Object
Set regex = CreateObject("VBScript.RegExp")

regex.pattern = pattern
regex.Global = False

If regex.test(inputString) Then
Set GetRegExpMatch = regex.Execute(inputString)(0)
Else
Set GetRegExpMatch = Nothing
End If
End Function

Private Function GetHTMLContent(ByVal url As String) As String
Dim xhr As Object
Set xhr = CreateObject("MSXML2.XMLHTTP")

xhr.Open "GET", url, False
xhr.Send

If xhr.Status = 200 Then
GetHTMLContent = xhr.ResponseText
Else
GetHTMLContent = ""
End If

Set xhr = Nothing
End Function

If necessary i can provide the html code from one colour printer
Any ideas ?? Thanks in advance
 
I just used the same GetHTMLContent call in your OP, assuming you were using the full URL.

Here is a better method using the HTML library. Also, it doesn't rely on looking for specific numbered "title" substrings (1,3,5,7), which I suspect would fail if any of the toners is 100%, because that would need only one <td title="60%" width="60%" bgcolor="#00ffff">&nbsp;</td> element, instead of 2 elements, and therefore the number sequence would be different. Note that this code is specific to the HTML in post #5 and you would have to write specific code for the HTML for the other printers.

This code requires a reference to Microsoft HTML Object Library, set via Tools -> References in the VBA editor.

VBA Code:
Private Sub GetToner_Click()
 
    ' Get the IP address from TextBox2
    Dim ipAddress As String
    'ipAddress = TextBox2.Value
 
    ' Get the HTML content from the printer's web page
    Dim htmlContent As String
    htmlContent = GetHTMLContent("http://" & ipAddress & "/cgi-bin/dynamic/printer/PrinterStatus.html")
 
    Dim HTMLdoc As HTMLDocument
    Dim colourLevel As String
 
    Set HTMLdoc = New HTMLDocument
    HTMLdoc.body.innerHTML = htmlContent
 
    colourLevel = Get_Toner_Level(HTMLdoc, "Cartucho ciano")
    If colourLevel <> "" Then
        TextBox5.Value = colourLevel
    Else
        MsgBox "'Cartucho ciano' not found", vbExclamation
    End If
 
    colourLevel = Get_Toner_Level(HTMLdoc, "Cartucho magenta")
    If colourLevel <> "" Then
        TextBox6.Value = colourLevel
    Else
        MsgBox "'Cartucho magenta' not found", vbExclamation
    End If
 
    colourLevel = Get_Toner_Level(HTMLdoc, "Cartucho Amarelo")
    If colourLevel <> "" Then
        TextBox7.Value = colourLevel
    Else
        MsgBox "'Cartucho Amarelo' not found", vbExclamation
    End If
 
    colourLevel = Get_Toner_Level(HTMLdoc, "Cartucho Preto")
    If colourLevel <> "" Then
        TextBox8.Value = colourLevel
    Else
        MsgBox "'Cartucho Preto' not found", vbExclamation
    End If
    
End Sub


Public Function Get_Toner_Level(HTMLdoc As HTMLDocument, colourTitle As String) As String

    'Return colour level value (e.g. 60%) for the specified colour title
 
    '<table width="80%" height="25" border="0">
    ' <tbody>
    '   <tr>
    '      <td colspan="2" width="30%" nowrap=""><b>Cartucho ciano</b></td>
    '   </tr>
    '   <tr>
    '      <td width="25%">
    '         <table width="160" height="23" cellspacing="0" bordercolor="#000000" border="1">
    '            <tbody>
    '               <tr>
    '                  <td title="60%" width="60%" bgcolor="#00ffff">&nbsp;</td>
    '                  <td title="60%" width="40%" bgcolor="#ffffff">&nbsp;</td>
    '               </tr>
    '            </tbody>
    '         </table>
    '      </td>
    '   </tr>
    ' </tbody>
    '</table>

    Dim colourTable As HTMLTable
    Dim tCell As HTMLTableCell
    Dim elem As HTMLGenericElement
 
    Get_Toner_Level = ""
 
    'Find td element containing the specified colour title and get its table
 
    Set colourTable = Nothing
    For Each tCell In HTMLdoc.getElementsByTagName("td")
        If tCell.getElementsByTagName("td").Length = 0 Then 'innermost table cell
            If InStr(1, tCell.innerText, colourTitle, vbTextCompare) Then
                'Found it - backtrack to parent table
                Set elem = tCell
                Do
                    Set elem = elem.parentElement
                Loop Until elem.tagName = "TABLE"
                Set colourTable = elem
            End If
        End If
    Next
    
    If Not colourTable Is Nothing Then
 
        'Found the table for this colour title. Extract the 'title' attribute value (e.g. 60%) from the first td element which has the 'title' attribute
        '<td title="60%" width="60%" bgcolor="#00ffff">&nbsp;</td>
        '<td title="60%" width="40%" bgcolor="#ffffff">&nbsp;</td>
            
        Get_Toner_Level = colourTable.querySelector("td[title]").getAttribute("title")
    
    End If
    
End Function



You would have to write VBA code which is specific to the HTML for each printer. I'm not going to do that for you, but hopefully my code above and other HTML parsing code on this forum should be enough to get you started.
Thanks for everything John , i've tried the code above and returns an error Run-time error '-2147012891 (80072ee5)' Method open of object iserverXMLHTTPRequest2 failed . I' ve tested in the printer that worked .
The error is in this line

xhr.Open "GET", url, False


I will extract the HTML code for the differente models , but how can i integrate all of that in one function ????
 
Upvote 0

Excel Facts

Do you hate GETPIVOTDATA?
Prevent GETPIVOTDATA. Select inside a PivotTable. In the Analyze tab of the ribbon, open the dropown next to Options and turn it off
i've tried the code above and returns an error Run-time error '-2147012891 (80072ee5)' Method open of object iserverXMLHTTPRequest2 failed . I' ve tested in the printer that worked .
The error is in this line

xhr.Open "GET", url, False
Is url correct?

I will extract the HTML code for the differente models , but how can i integrate all of that in one function ????
I would use separate functions for each different printer.
 
Upvote 0
Thanks for everything John , i've tried the code above and returns an error Run-time error '-2147012891 (80072ee5)' Method open of object iserverXMLHTTPRequest2 failed . I' ve tested in the printer that worked .
The error is in this line

xhr.Open "GET", url, False


I will extract the HTML code for the differente models , but how can i integrate all of that in one function ????
the URL is correct , it was on the same printer that the code worked , this time didn't
 
Upvote 0
the URL is correct , it was on the same printer that the code worked , this time didn't
Got the bug ... when i copied your code , i didn't saw an error on the url , now it's fixed .
Lot of work now to go to the different model e extract the html code
But it was an excelente help from you , John , Thank you so much
 
Upvote 0
John , final question , since i have 90% of mono printers , and the observation that i made from the HTML code from 6 different models is the same as the colour one ( only doesn't have the magenta cyan and yellow ) , and uses the same htmlContent = GetHTMLContent("http://" & ipAddress & "/cgi-bin/dynamic/printer/PrinterStatus.html") , how can i change the code to give me the black toner ? With the code to mono , iwill concetrate on the printers that dont' have the same HTML code
 
Upvote 0
the observation that i made from the HTML code from 6 different models is the same as the colour one ( only doesn't have the magenta cyan and yellow ) , and uses the same htmlContent = GetHTMLContent("http://" & ipAddress & "/cgi-bin/dynamic/printer/PrinterStatus.html") , how can i change the code to give me the black toner ?

If the web page HTML really is the same (exactly the same structure), such that the Get_Toner_Level correctly returns the correct value, then all you need is to call it for mono like this:

VBA Code:
Private Sub GetMonoToner_Click()
  
    ' Get the IP address from TextBox2
    Dim ipAddress As String
    'ipAddress = TextBox2.Value
  
    ' Get the HTML content from the printer's web page
    Dim htmlContent As String
    htmlContent = GetHTMLContent("http://" & ipAddress & "/cgi-bin/dynamic/printer/PrinterStatus.html")
  
    Dim HTMLdoc As HTMLDocument
    Dim colourLevel As String
  
    Set HTMLdoc = New HTMLDocument
    HTMLdoc.body.innerHTML = htmlContent
  
    colourLevel = Get_Toner_Level(HTMLdoc, "Cartucho Preto")
    If colourLevel <> "" Then
        TextBox8.Value = colourLevel
    Else
        MsgBox "'Cartucho Preto' not found", vbExclamation
    End If
      
End Sub
 
Upvote 0
If the web page HTML really is the same (exactly the same structure), such that the Get_Toner_Level correctly returns the correct value, then all you need is to call it for mono like this:

VBA Code:
Private Sub GetMonoToner_Click()
 
    ' Get the IP address from TextBox2
    Dim ipAddress As String
    'ipAddress = TextBox2.Value
 
    ' Get the HTML content from the printer's web page
    Dim htmlContent As String
    htmlContent = GetHTMLContent("http://" & ipAddress & "/cgi-bin/dynamic/printer/PrinterStatus.html")
 
    Dim HTMLdoc As HTMLDocument
    Dim colourLevel As String
 
    Set HTMLdoc = New HTMLDocument
    HTMLdoc.body.innerHTML = htmlContent
 
    colourLevel = Get_Toner_Level(HTMLdoc, "Cartucho Preto")
    If colourLevel <> "" Then
        TextBox8.Value = colourLevel
    Else
        MsgBox "'Cartucho Preto' not found", vbExclamation
    End If
     
End Sub
Thank you so much John . I've tried the code above , and always returns MsgBox "'Cartucho Preto' not found".

But after some digging and several attempts , i finally got it working at least to almost 10% of the printers ( tested in 15 printers , and they all return the correct value ) with this code :


Private Sub GetToner_Click()

' Get the IP address from TextBox2
Dim ipAddress As String
ipAddress = TextBox2.Value

' Get the HTML content from the printer's web page
Dim htmlContent As String
htmlContent = GetHTMLContent("http://" & ipAddress & "/cgi-bin/dynamic/printer/PrinterStatus.html")

' Update the TextBoxes with toner levels

TextBox8.Value = Get_Toner_Level(3, htmlContent) ' Black Toner Level
TextBox8.ForeColor = vbBlack

End Sub


Private Function Get_Toner_Level(titleNumber As Long, HTML As String) As String

Dim p1 As Long, p2 As Long
Dim i As Long

'Find nth "title=" substring and return the nn% number (e.g. 60%)

'<td width="60%" bgcolor="#000000">&nbsp;</td>

p1 = 0
For i = 1 To titleNumber
p1 = InStr(p1 + 1, HTML, "width=", vbTextCompare)
Next
p1 = p1 + Len("width=")
p2 = InStr(p1, HTML, "%")

Get_Toner_Level = Replace(Mid(HTML, p1, p2 - p1), Chr(34), "")

End Function


Now here comes the other part , this code works if this conditions are valid :

htmlContent = GetHTMLContent("http://" & ipAddress & "/cgi-bin/dynamic/printer/PrinterStatus.html")

'<td width="60%" bgcolor="#000000">&nbsp;</td>
p1 = 0
For i = 1 To titleNumber
p1 = InStr(p1 + 1, HTML, "width=", vbTextCompare)
Next
p1 = p1 + Len("width=")
p2 = InStr(p1, HTML, "%")


The other printer ( recent models ) the htmlContent is this :

htmlContent= GetHTMLContent("http://" & ipAddress & "#/status" it has the part of the html with toner values is
<div class="progress-inner BlackGauge" role="img" title="100%" aria-labelledby="100%"> .
I've tried to replace in the working code , but always returns an error Invalid Procedure Call or Argumentin this line
Get_Toner_Level = Replace(Mid(HTML, p1, p2 - p1), Chr(34), "")

How can i create some kind of if condition to the code , so when its cgi-bin/dynamic/printer/PrinterStatus.html he runs like this

For i = 1 To titleNumber
p1 = InStr(p1 + 1, HTML, "width=", vbTextCompare)
Next
p1 = p1 + Len("width=")
p2 = InStr(p1, HTML, "%")


And when it's "http://" & ipAddress & "#/status" , it runs in other way ??
 
Upvote 0
Thank you so much John . I've tried the code above , and always returns MsgBox "'Cartucho Preto' not found".

Well, what is the correct heading for mono toner? That text is passed as the 2nd argument to the Get_Toner_Level function: Get_Toner_Level(HTMLdoc, "Cartucho Preto") and the function looks for the table cell containing that text - there are comments in the code to help you understand it. It's up to you to supply the correct heading text, just like I have for the other colours. The function should work if the mono printer HTML has exactly the same structure as the colour printer HTML I wrote it for.


How can i create some kind of if condition to the code , so when its cgi-bin/dynamic/printer/PrinterStatus.html he runs like this

For i = 1 To titleNumber
p1 = InStr(p1 + 1, HTML, "width=", vbTextCompare)
Next
p1 = p1 + Len("width=")
p2 = InStr(p1, HTML, "%")


And when it's "http://" & ipAddress & "#/status" , it runs in other way ??

As I said previously, I would have separate functions which parse the HTML, depending on the printer. You could have a lookup table, either in a worksheet, or in the code (an array) which tells the code which parsing function to use for that particular printer, using If ... Then ... ElseIf .... Else ... End If or Select Case statements.
 
Upvote 0
Well, what is the correct heading for mono toner? That text is passed as the 2nd argument to the Get_Toner_Level function: Get_Toner_Level(HTMLdoc, "Cartucho Preto") and the function looks for the table cell containing that text - there are comments in the code to help you understand it. It's up to you to supply the correct heading text, just like I have for the other colours. The function should work if the mono printer HTML has exactly the same structure as the colour printer HTML I wrote it for.
Jonh , i think i've found the problema , some printers are in english and some are in portuguese . So when the code tries to find "Cartucho Preto" and the page is in english , doen't find , because what appears is Black toner.

Now i'm in a fight with the others , i made several tries , with no luck , in this parte , where i change to :
htmlContent= GetHTMLContent("http://" & ipAddress & "#/status" it has the part of the html with toner values is
<div class="progress-inner BlackGauge" role="img" title="100%" aria-labelledby="100%"> .

I know that this title="100%" aria-labelledby="100%" is the value of the toner

I'm going to continue digging and triyng , and then i let you know when i success

Thank you so much
 
Upvote 0
I'm feeling so close , right now , to the other printer ( the ones with htmlContent = GetHTMLContent("http://" & ipAddress & "/#/Status") ) , i got no error but no values
Thanks to John , who put me in the right direction , i checked the html code and tested with the getelementsbytagname
Right now the code i have :
Private Sub GetToner_Click()

' Get the IP address from TextBox2
Dim ipAddress As String
ipAddress = TextBox2.Value

' Get the HTML content from the printer's web page
Dim htmlContent As String
htmlContent = GetHTMLContent("http://" & ipAddress & "/#/Status")

' Update the TextBoxes with toner levels

TextBox8.Value = Get_Toner_Level(3, htmlContent) & "%" ' Black Toner Level
TextBox8.ForeColor = vbBlack

End Sub

Private Function Get_Toner_Level(titleNumber As Long, HTML As String) As String
Dim doc As Object ' InternetExplorer
Dim element As Object ' HTMLSpanElement
Dim elements As Object ' IHTMLElementCollection
Dim i As Long

' Create a new instance of InternetExplorer
Set doc = CreateObject("InternetExplorer.Application")

' Navigate to a blank page or set the HTML content
doc.navigate "about:blank"

' Wait until the page finishes loading
Do While doc.Busy Or doc.readyState <> 4
DoEvents
Loop

' Set the HTML content
doc.document.body.innerHTML = HTML

' Get all the span elements
Set elements = doc.document.getElementsByTagName("span")

' Loop through the elements and find the desired element
For i = 0 To elements.Length - 1
' Check if the element has the class "dataText"
Debug.Print elements.Item(i).className ' Debug output to check the element class names

If elements.Item(i).className = "dataText" Then
' Assign the element to the variable
Set element = elements.Item(i)
Exit For
End If
Next i

' Check if the element exists
If Not element Is Nothing Then
' Extract the inner text of the span element
Get_Toner_Level = element.innerText
End If

' Clean up
doc.Quit
Set doc = Nothing
Set element = Nothing
Set elements = Nothing
End Function

The HTML code that i want it is this :
HTML:
<li id="TonerSupplies" data-node="TonerSupplies" class="child-row">
   <div class="supplyStatusContainer" data-init="initSupplyStatusContainer(this)">
      <div class="contentRow" role="gridcell">
         <div class="contentHeader" role="heading">
            <span class="translated" data-textid="67527" tabindex="-1">
            Black Cartridge
            </span>
            <br>
         </div>
         <div class="contentBody" role="presentation">
            <div class="progress" role="presentation" tabindex="" data-deviceid="8-1">
               <div class="progress-inner BlackGauge" role="img" title="17%" aria-labelledby="17%">
                  <div class="progress-slider" style="width: 40px; overflow: hidden;">
                     <span class="dataText">17</span>
                  </div>
               </div>
            </div>
         </div>
      </div>
   </div>
</li>

In this case , the value that i expected is 17 .

What am i missing ????
 
Upvote 0

Forum statistics

Threads
1,224,820
Messages
6,181,157
Members
453,021
Latest member
Justyna P

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top