Get IP address of a networked printer?

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?

Thanks for any help.

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.

Hello!

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.

HTH

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):

dnssd://HP%20LaserJet%204000%20Series%20%5A6789055%5D._printer._tcp.local./

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.

Hello!

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.

I should have made this clear earlier. Apologies!

Hello!

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. :slight_smile:

It is a tough one!

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.

Hi,

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

The code is code signed for Gatekeeper

You can download Bonjour Events here

Stefan,

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!

Thank you again for creating this program.

this line passes the parameters


scan type "_afpovertcp._tcp" in domain "local"

so enter for example _printer._tcp instead of _afpovertcp._tcp (the underscore characters are crucial)

after the repeat loop the element services contains all detected bonjour services.

If the printers are not located in the local network, omit the in domain parameter or pass the appropriate domain

Ah! Now I understand! Thank you again.

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!

Attempting to coerce HTML entities to a string, I found this here http://harvey.nu/applescript_url_decode_routine.html:

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?

perl can do that


set escapedURL to "dnssd://HP%20LaserJet%20P3010%20Series%20%5B39167A%5D._printer._tcp.local."
do shell script "perl -e 'use URI::Escape; print uri_unescape(\"" & escapedURL & "\")';"
--> dnssd://HP LaserJet P3010 Series [39167A]._printer._tcp.local.


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!

It’s pretty easy to handle URL schemes in Cocoa.

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