I have been trying to solve this without success, and would be grateful for any advice.
For a script I am writing, I want to display a list of currently installed printers, ask the user to select one, and then find its IP address on the network if it has one, or display a message if the selected printer is not a networked printer with an IP address. I know how to display a list of printers and obtain the printer name and queue name, but I don’t know how to determine whether or not the printer has an IP address.
I hoped I could get this information from lpstat, but I can’t find it there. Am I missing something obvious?
Try getting it from the Printer Setup Utility which is scriptable. Name of every printer should show printer names, but I’m not sure about IP addresses – I don’t have a networked printer to test.
I guess the printer must have a network name, for you to see it by bonjour or printerservices, and this is totally untested. But when it has a network name, you should be able to send a dns query to get its name.
You’ll have to google a little bit, to understand how to use telnet, or something else to send that query. I know for sure that the technique is used for testing the setup of DNS servers, (Domain Name System) servers), so have a look at such tutorials.
Thanks for these suggestions. As far as I can tell, a networked printer’s IP address is not known to Printer Setup Utility. When I use the CUPS configuration page (http://localhost:631/printers) - after enabling the browser interface under Mountain Lion - I can see that my networked printer is listed like this (I’ve changed the string for privacy reasons):
If I know the queue name of the printer, I think I can find that string by combining lpstat and grep, but what I still need is some way to figure out what IP address corresponds to that dnssd:// string.
I’ll keep searching, but if someone has an inspiration, and I’ll be grateful to hear about it.
I see from your printers network name, that it belongs to your local domain, so I think you don’t have to look for the ip addres, you just configure your printer and router to use one static ip addres, that is, avoid the usage of dhcp for allocating an ip address to your printer dynamically, that way your whole problem disappears.
That is the easy approach, if your router doesn’t have a dns server built in, which you can use, with your printers network name, to obtain the ip address like I stated above. You can see that your printers url starts with dns? But you’ll have to read up to be able to query the dns server for the Ip address.
You are of course right about my own system, but I am trying to write a script that will let a user on his or her own machine select a printer from a list, and then my script will perform various operations that require the IP address (specifically, set up the printer as an lpr printer in the SheepShaver emulator running OS 8.6 or 9.0.4). So I need to be able to determine the IP address of a (local) printer on someone else’s network.
Until someone turns up with something really smart, to figure out how to get the ip-address, I’d circumvent the whole thing, and print out via printservices.
If sheepsaver is a box doing dos, you can redirect the printer to a file, grab the file, and then print it out with the printervices, if that is possible.
Getting the ip-address out of cups, are ok, if you have entered the ip-address into cups up front. (I’m at SL, things may have changed.)
To use Netstat, and do a grep for port 631 (this is protocol dependant), you would have to print something while you check for the ip address.
SheepShaver is a PowerPC emulator that runs Mac OS (through 9.0.4). I already can print by printing to a file in OS X and running a folder action script, but printing to an IP printer is much faster. It’s easy to make it work by manually setting up the printer in SheepShaver/MacOS, but I am trying to automate the process for inexperienced users.
today I wrote a little faceless scriptable app to scan bonjour services called Bonjour Events.
The dictionary is very tiny.
There is a class service containing the properties
domain get text The domain of the service.
host get text The host of the service.
IPv4 address get text The IPv4 address of the service.
IPv6 address get text The IPv6 address of the service.
name get text The name of the service.
port get integer The port of the service.
type get text The type of the service.
and one command scan with two parameters
in domain optional text The domain to search in e.g. local.
type required text The type to search for e.g. _afpovertcp._tcp.
As the scan process works asynchronously, a repeat loop to wait for finishing is required.
{I haven’t figured out yet how to provide an event handler)
This is a sample code
try
tell application "Bonjour Events"
scan type "_afpovertcp._tcp" in domain "local"
repeat until browser finished
delay 0.5
end repeat
if (count services) > 0 then
set theService to service 1
set theAddress to IPv4 address of theService
end if
-- Bonjour Events quits automatically after 2 minutes of inactivity
end tell
on error errorMessage number errorNumber
display dialog "An error occured: " & errorMessage & " (" & errorNumber & ")"
end try
You can figure out the type string with BonjourBrowser
This is superb work and will be very useful to many people. Thank you!
At the moment, though, I can only make it return the address of my Mac itself. I see from Bonjour Browser that my networked printers (with their IP addresses) are listed under the types _ipp._tcp. and _printer._tcp and _pdl-datastream._tcp, but I can’t see how to get a list of the items inside those types and their attributes.
Probably I simply don’t understand how your code works, and I apologize for bothering you twice for information!
My next step was to get the names and IP addresses of all my IP printers, so I modified your script to look like the following. (I am certain this is clumsy and inefficient, but it is the best I could do.)
try
tell application "Bonjour Events"
scan type "_ipp._tcp" in domain "local"
repeat until browser finished
delay 0.5
end repeat
set nameList to {}
set addressList to {}
set serviceNumber to 0
-- set serviceCount to 0
set serviceCount to (count services)
if (count services) > 0 then
repeat until serviceNumber is equal to serviceCount
set serviceNumber to (serviceNumber + 1)
set theService to service serviceNumber
set theName to name of theService
set the end of nameList to theName
set theAddress to IPv4 address of theService
set the end of addressList to theAddress
end repeat
end if
quit
-- Bonjour Events quits automatically after 2 minutes of inactivity
end tell
on error errorMessage number errorNumber
display dialog "An error occured: " & errorMessage & " (" & errorNumber & ")"
end try
I added the repeat loop and also told Bonjour Events to quit, because, if I did not, and ran the script again while Bonjour Events was still open, it reported the wrong number of services. (In other words, if the first time I ran it, Bonjour Events reported 3 services, it then reported 6, then 9, etc. This could be fixed by adding the “quit” line.)
Now my next step is to figure out how to match the information about the printer known to Printer Setup Utility or to lpstat to the results from this program so that I can match a printer name or queue name to its IP address.
Thank you again for this program, which does exactly what I was hoping for!
I updated the program to clear the services list before starting the scan. Same link.
The script can be simplified
try
tell application "Bonjour Events"
scan type "_ipp._tcp" in domain "local"
repeat until browser finished
delay 0.5
end repeat
if (count services) > 0 then
set nameList to name of services
set addressList to IPv4 address of services
else
set nameList to {}
set addressList to {}
end if
quit
-- Bonjour Events quits automatically after 2 minutes of inactivity
end tell
on error errorMessage number errorNumber
display dialog "An error occurred: " & errorMessage & " (" & errorNumber & ")"
end try
I’ve made some further progress on this, and have figured out at least part of the problem of matching a printer name to its IP address.
First, I use this script (in a more elaborate form) to ask the user which printer he wants to work with:
set noPrinters to 0
try
set printerNames to (do shell script "lpstat -l -p | grep -i Description: |awk -F'Description: ' '{print $2}' ") -- as list
on error
set noPrinters to 1
end try
if printerNames = "" then
set noPrinters to 1
end if
if noPrinters is 0 then
try
set queueNames to (do shell script "lpstat -a | awk -F' accepting' '{print $1}'") -- as list
on error
set noPrinters to 1
end try
end if
if noPrinters = 0 then
try
set printerList to (every paragraph of printerNames) as list
set queueList to (every paragraph of queueNames) as list
end try
tell me to activate
set thePrinter to (choose from list printerList with title "Printers" with prompt "Choose a printer:")
if thePrinter is false then
tell me to activate
display dialog "Printer setup cancelled."
error number -128
else
set thePrinter to item 1 of thePrinter
set item_num to my list_position(thePrinter, printerList)
set theQueue to item item_num in queueList
end if
end if
on list_position(this_item, this_list)
repeat with i from 1 to the count of this_list
if item i of this_list is this_item then return i
end repeat
return 0
end list_position
Now that I have the correct queue name, I can run this shell command:
set dnsString to do shell script "lpstat -v " & theQueue
This returns something like this:
What I now want to do is extract everything in this string between “dnssd://” and “.printer.tcp.local./” and then compare that extracted string with the list that I got from your modified Applescript; that will let me get the corresponding IP address of the printer.
With a little effort, I think I can figure out how to extract the desired string from the result. What I do not know how to do is convert an URL-style string with HTML entities (%20, etc.) into a text string. If anyone knows the answer to that, I will be very grateful.
Thank you again for making this project possible!
EDIT: And thank you for the simplified script - it taught me some very useful lessons!
set theSampleURL to "HP%20LaserJet%20P3010%20Series%20%5B39167A%5D"
set theText to text returned of (display dialog "decode what" default answer theSampleURL)
set theTextDec to urldecode(theText) of me
display dialog theTextDec default answer theTextDec
on urldecode(theText)
set sDst to ""
set sHex to "0123456789ABCDEF"
set i to 1
repeat while i ≤ length of theText
set c to character i of theText
if c = "+" then
set sDst to sDst & " "
else if c = "%" then
if i > ((length of theText) - 2) then
display dialog ("Invalid URL Encoded string - missing hex char") buttons {"Crap..."} with icon stop
return ""
end if
set iCVal1 to (offset of (character (i + 1) of theText) in sHex) - 1
set iCVal2 to (offset of (character (i + 2) of theText) in sHex) - 1
if iCVal1 = -1 or iCVal2 = -1 then
display dialog ("Invalid URL Encoded string - not 2 hex chars after % sign") buttons {"Crap..."} with icon stop
return ""
end if
set sDst to sDst & (ASCII character (iCVal1 * 16 + iCVal2))
set i to i + 2
else
set sDst to sDst & c
end if
set i to i + 1
end repeat
return sDst
end urldecode
It seems to work, but I wonder if there’s a more efficient method?
That is a lot more efficient than the long routine I posted earlier. Thank you again!
Now, to figure out how to extract everything between (but not including) dnnsd:// and ._printer._tcp.local. I know there are plenty of string-manipulation routines available, and it’s probably time for me to learn some that I don’t know. But if anyone has a quick, efficient answer, I would be very grateful for it.
I am now very close, but there is one detail that I can’t figure out. Probably it is a beginner’s error. In the code below, the dialog that says “The next lines do not work” point to the problem!
set noPrinters to 0
try
set printerNames to (do shell script "lpstat -l -p | grep -i Description: |awk -F'Description: ' '{print $2}' ") -- as list
on error
set noPrinters to 1
end try
if printerNames = "" then
set noPrinters to 1
end if
if noPrinters is 0 then
try
set queueNames to (do shell script "lpstat -a | awk -F' accepting' '{print $1}'") -- as list
on error
set noPrinters to 1
end try
end if
if noPrinters = 0 then
try
set printerList to (every paragraph of printerNames) as list
set queueList to (every paragraph of queueNames) as list
end try
tell me to activate
set thePrinter to (choose from list printerList with title "Printers" with prompt "Choose a printer:")
if thePrinter is false then
tell me to activate
display dialog "Printer setup cancelled."
error number -128
else
set thePrinter to item 1 of thePrinter
set item_num to my list_position(thePrinter, printerList)
set theQueue to item item_num in queueList
end if
set dnsURL to do shell script "lpstat -v " & theQueue
set dnsString to (do shell script "perl -e 'use URI::Escape; print uri_unescape(\"" & dnsURL & "\")';")
-- display dialog "dnsString: " & dnsString
try
tell application "Bonjour Events"
scan type "_ipp._tcp" in domain "local"
repeat until browser finished
delay 0.5
end repeat
if (count services) > 0 then
set nameList to name of services
set addressList to IPv4 address of services
else
set nameList to {}
set addressList to {}
end if
quit
-- Bonjour Events quits automatically after 2 minutes of inactivity
end tell
on error errorMessage number errorNumber
display dialog "An error occurred: " & errorMessage & " (" & errorNumber & ")"
end try
repeat with currentItem in nameList
if currentItem is in dnsString then
display dialog "Next lines do not work!"
set dnsNumber to my list_position(currentItem, nameList)
set printerIP to item dnsNumber in addressList
display dialog printerIP
end if
end repeat
end if
on list_position(this_item, this_list)
repeat with i from 1 to the count of this_list
if item i of this_list is this_item then return i
end repeat
return 0
end list_position
I am clearly doing something wrong in my call to the list_position routine, but I can’t figure out what it is!
I added a command host for URL in Bonjour Events.
It returns the unescaped host name without the type/domain portion
Version 1.1 - same link
set theURL to "dnssd://HP%20LaserJet%20P3010%20Series%20%5B39167A%5D._printer._tcp.local./"
tell application "Bonjour Events"
host for URL theURL
end tell
--> HP LaserJet P3010 Series [39167A]
Solved it (the problem two messages above)! Replace the relevant code in the previous post with this:
repeat with currentItem in nameList
if currentItem is in dnsString then
set currentItem to currentItem as item
set dnsNumber to my list_position(currentItem, nameList)
set printerIP to item dnsNumber in addressList
display dialog printerIP
end if
end repeat