Get IP address of a networked printer?

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


I downloaded the latest version, but got a syntax error message when I tried to run this script:

“A forURL theURL can’t go after this host.”

I’m completely baffled by this!

Have you deleted the old version and relaunched AppleScript Editor to update the dictionary?

I deleted the old version but neglected to close down and restart the AppleScript Editor. Thank you for correcting a beginner’s mistake!

Thanks to StefanK, who did all the hard work, I have finally written a script that should return the IP address of a networked printer. Here is the code, which is surely sloppy and inefficient but seems to get the job done. Many thanks again to StefanK and to everyone else at MacScripter.net whose work I have used in this script.

-- Get the IP address of a networked IP printer
-- by Edward Mendelson, using code adapted from many posts at MacScripter.net
-- requires "Bonjour Events" by Stefan Klieme, downloadable here:
-- http://www.klieme.ch/pub/Bonjour%20Events.zip

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 & "\")';")
	
	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
	
	set ipPrinterFound to false
	repeat with currentItem in nameList
		if currentItem is in dnsString then
			set ipPrinterFound to true
			set currentListItem to currentItem as item
			set dnsNumber to my list_position(currentListItem, nameList)
			set printerIP to item dnsNumber in addressList
			display dialog currentItem & return & return & "has the IP address:" & space & printerIP
			exit repeat
		end if
	end repeat
	
	if ipPrinterFound is false then
		display dialog thePrinter & " does not seem to support IP printing."
		error number -128
	end if
	
else
	tell me to activate
	display dialog "No printers found!"
	error number -128
	
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

It is just a pity that Stefan’s utility doesn’t work with Snow Leopard :frowning:

I set the deployment target to 10.5. Now it should work down to Leopard (Intel)

Thanks Stefan! :slight_smile: