Bonjour Events, a scriptable application to scan Bonjour Services

In this thread I posted a small faceless scriptable app to scan Bonjour services for a given type and domain and have access to the (DNS resolved) properties.

Meanwhile I made a few improvements:

The scan command waits now for finishing the scanning process, a repeat loop isn’t needed any more.
To control the timing you can add an optional parameter time out after .

The result of the scan command is now a record:

scan error : missing value or the error reason (text)
timed out : like gave up in display dialog (boolean)
number of services : the number of detected services (integer)

If number of services is not zero, services contains instances of a Bonjour service
including the properties domain, host, IPv4 address, IPv6 address, name, port, and service type

As a possible error is passed in the result record, a try block could be omitted too.

Sample code:


tell application "Bonjour Events"
	scan for type "_afpovertcp._tcp" in domain "local" time out after 5.0
	set {scan error:scanError, timed out:timedOut, number of services:numberOfServices} to result
	if scanError is not missing value then
		-- do some error handling
	else if timedOut then
		-- handle time out
	else if numberOfServices > 0 then
		tell service 1
			set ipv4Address to IPv4 address
			set theHost to host
		end tell
	end if
	-- Bonjour Events quits automatically after 2 minutes of inactivity
end tell

Requirements: Intel Mac - 10.5 and higher

Download: Bonjour Events (The app is code signed)

Hi. I’m on 10.8. Downloaded and started Bonjour Events. Opened the applescript and it doesn’t compile. It highlights the word “type” in “scan type” with the error “Syntax error: Expected end of line but found identifier”.

EDIT: never mind. I checked activity monitor to see if Bonjour Events was running… and it wasn’t. I then tried the applescript again and now it compiles. Not sure what happened but it’s working.

I guess a new scriptable app must be launched once to be registered to the AppleScript environment

No, I think I figured it out. Somehow the word type is causing the problem. It seems there are 2 types in your applescript dictionary, the scan type and in the service record there is a type property. Somehow they’re getting mixed up. Here’s why I say this. Here’s a portion of my script…

tell application "Bonjour Events"
	scan type "_afpovertcp._tcp" in domain "local" time out after 5.0
	set {scan error:scanError, timed out:timedOut, number of services:numberOfServices} to result
	set theServices to {}
	repeat with aService in services
		tell aService
			set a to IPv4 address
			set h to host
			set p to port
			set t to type of aService
			set end of theServices to {h, a, p, t}
		end tell
	end repeat
	return theServices
end tell

Notice the line “set t …”. I need to specify “of aService” otherwise I do not get a proper returned value from the record. Since that’s the same word that the script had trouble compiling on then I think it has something to do with “type” having multiple meanings to your app.

thanks Hank, I’m using the original property names from the documentation,
I’ll look for a solution to avoid that terminology clash

No problem and thanks for the tool.

One more question. Is there a way to scan for any service type rather than specifying one? Maybe it’s an easy update? In general I’d like to know any available service on my network and would prefer not to loop through all of them… I found a complete list here.

Of course it’s possible (see Bonjour Browser), but it’s quite expensive,
because you have to instantiate one browser instance for each type, there is no “searchAll” or “searchInList” method.

Resolving the terminology clash I changed the command
scan type to scan for type
and the property of service
type to service type

the keyword type is internally used in AppleScript, probably that caused the problem

Version 1.3, same link

Your 2 changes worked. Your suggestion about the possible problem with “type” makes sense. Thanks for the upgrades. You’ve got a nice tool here.

Finally I discovered how to implement an callback event handler (the Cocoa Scripting documentation is very misty regarding this issue), so you can use Bonjour Events asynchronously.

This option can be enabled by using the (optional) parameter target passing the reference to a script (usually me) and implementing the did detect services event handler


using terms from application "Bonjour Events"
	on did detect services serviceList for type forType with result replyResult
		
		set {scan error:scanError, timed out:timedOut, number of services:numberOfServices} to replyResult
		if scanError is not missing value then
			display dialog "do some error handling"
		else if timedOut then
			display dialog "event timed out"
		else if numberOfServices > 0 then
			display dialog "Number of Services: " & numberOfServices
			repeat with aService in serviceList
				tell aService
					set serviceName to its name
					set ipv4Address to IPv4 address
					set theHost to host
					set theType to service type
				end tell
				display dialog "Name: " & serviceName & return & "Host: " & theHost & return ¬
					& "IPV4 Address: " & ipv4Address & return & "Type: " & theType
			end repeat
		end if
		
	end did detect services
end using terms from


tell application "Bonjour Events"
	scan for type "_afpovertcp._tcp" in domain "local" time out after 5.0 target me
end tell


Version 1.4, same link as in the first posting

[ANN] Version 2 of Bonjour Events

the main new feature is the ability to create multiple browsers.
The (arbitrary) name property has to be specified in the make new line, the other properties service type, domain and time out can be set in separate lines or in the scan command.

Default value for domain is an empty string (any domain). To search in the local network, pass “local”.
Default value for time out is 5 (seconds).
The service type must be a Bonjour type string like _afpovertcp._tcp.

The synchronous version returns a record:

scan error : missing value or the error reason (text)
timed out : like gave up in display dialog (boolean)
bonjour services : a list of the service instances or missing value if an error occurs

Sample code:


tell application "Bonjour Events"
	-- create new browser instance, the name property is mandatory
	set AFPBrowser to make new browser with properties {name:"MyBrowser"}
	tell AFPBrowser
		scan for type "_afpovertcp._tcp." in domain "local" time out after 10.0 -- default time out value is 5.0
	end tell
	(*
	
	alternatively set the properties in separate lines
	
		tell AFPBrowser
			set domain to "local" -- default is "" (any domain)
			set service type to "_afpovertcp._tcp"
			scan
		end tell
	*)
	
	set {scan error:scanError, timed out:timedOut, bonjour services:bonjourServices} to result
	if scanError is not missing value then
		-- do some error handling
	else if timedOut then
		-- handle time out
	else if (count bonjourServices) > 0 then
		set firstService to item 1 of bonjourServices
		tell firstService
			set ipv4Address to IPv4 address
			set theHost to host
		end tell
	end if
	-- Bonjour Events quits automatically after 5 minutes of inactivity
end tell

The asynchronous version provides two event handlers

on did appear services of browser aBrowser with result replyResult
on did disappear services of browser aBrowser hosts hostNames

The former returns the browser instance in the direct parameter aBrowser and the same record type as in the synchronous version in the replyResult parameter.
The latter returns the browser instance in the direct parameter aBrowser and a list of the disappeared host names (text) in the hostNames parameter.

To use the asynchronous version add the target parameter in the scan command with the value me.

Sample code:


using terms from application "Bonjour Events"
	on did appear services of browser aBrowser with result replyResult
		set {scan error:scanError, timed out:timedOut, bonjour services:serviceList} to replyResult
		set numberOfServices to (count serviceList)
		if scanError is not missing value then
			display dialog "do some error handling"
		else if timedOut then
			display dialog "event timed out"
		else if numberOfServices > 0 then
			display dialog (numberOfServices as text) & " services in browser " & name of aBrowser & " appeared"
			repeat with aService in serviceList
				tell aService
					set serviceName to its name
					set ipv4Address to IPv4 address
					set theHost to host
					set theType to service type
				end tell
				display dialog "Name: " & serviceName & return & "Host: " & theHost & return ¬
					& "IPV4 Address: " & ipv4Address & return & "Type: " & theType
			end repeat
		end if
	end did appear services of browser

	on did disappear services of browser aBrowser hosts hostNames
		set numberOfHosts to (count hostNames)
		if numberOfHosts > 0 then
			display dialog (numberOfHosts as text) & " services in browser " & name of aBrowser & " disappeared"
			repeat with aHost in hostNames
				display dialog "Name: " & aHost
			end repeat
		end if
	end did disappear services of browser
end using terms from


set bonjourType to "_afpovertcp._tcp"
tell application "Bonjour Events"
	-- create new browser instance, the name property is mandatory
	set AFPBrowser to make new browser with properties {name:"MyBrowser"}
	tell AFPBrowser
		scan for type bonjourType in domain "local" target me
	end tell
end tell


Note : Bonjour Events quits automatically after 5 minutes of inactivity in the synchronous version.
In the asynchronous version the quit delay property is set to 0 (infinite) when a browser starts scanning.
You can stop a browser in one of the event handlers in code with

if name of aBrowser is "MyBrowser" then tell aBrowser to stop

At the moment all browsers are stopped the quit delay property is reset to 300 (5 minutes)

You could also stop a browser from another script file


tell application "Bonjour Events"
	if exists browser "MyBrowser" then
		tell browser "MyBrowser" to stop
	end if
end tell

As long as Bonjour Events is running the created browser instances stay alive and send Apple Events until the application quits or the respective browser stops.

Download : Bonjour Events 2

Update: for consistency I changed the event handler definitions to


on did appear services serviceList of browser aBrowser with result replyResult
on did disappear services hostNames of browser aBrowser

extended sample code of the asynchronous version


using terms from application "Bonjour Events"
	on did appear services serviceList of browser aBrowser with result replyResult
		set {scan error:scanError, timed out:timedOut} to replyResult
		if scanError is not missing value then
			display dialog "do some error handling"
		else if timedOut then
			display dialog "event timed out"
		else if (count serviceList) > 0 then
			my notifyAppearedServices(aBrowser, serviceList)
		end if
	end did appear services
	
	
	on did disappear services hostNames of browser aBrowser
		set numberOfHosts to (count hostNames)
		if numberOfHosts > 0 then
			display dialog (numberOfHosts as text) & " services in browser " & name of aBrowser & " disappeared"
			repeat with aHost in hostNames
				display dialog "Name: " & aHost
			end repeat
		end if
	end did disappear services
end using terms from

set bonjourType to "_afpovertcp._tcp"
tell application "Bonjour Events"
	-- create new browser instance, the name property is mandatory
	if (exists browser "MyBrowser") then
		set AFPBrowser to browser "MyBrowser"
	else
		set AFPBrowser to make new browser with properties {name:"MyBrowser"}
	end if
	tell AFPBrowser
		if is scanning then
			set availableServices to services
			my notifyAppearedServices(it, availableServices)
		else
			scan for type bonjourType in domain "local" target me
		end if
	end tell
end tell

on notifyAppearedServices(theBrowser, theServices)
	tell application "Bonjour Events"
		display dialog ((count theServices) as text) & " services in browser " & name of theBrowser & " appeared"
		repeat with aService in theServices
			tell aService
				set serviceName to its name
				set ipv4Address to IPv4 address
				set theHost to host
				set theType to service type
			end tell
			display dialog "Name: " & serviceName & return & "Host: " & theHost & return ¬
				& "IPV4 Address: " & ipv4Address & return & "Type: " & theType
		end repeat
	end tell
end notifyAppearedServices


Download Version 2.1 (same link) : Bonjour Events 2.1

@StefanK - Is this application still available? I can’t find it anywhere on line. Thank you!

I can’t remember why I removed the archive from the server. I restored it, see the link in one of the previous posts. But I don’t know whether it’s still working or not. The creation date of the archive is 2014.

StefanK,

I can’t get your script above to compile

I got

Applescript Compile Error
Expected “given”, “into”, “with”, “without” or other parameter name but found identifier.

The error was on the second line…

on did appear services serviceList of browser aBrowser with result replyResult

@StefanK - Thank you for releasing the archive. Unfortunately, my script gives an error message with the Bonjour Events 2 version, while the original version works perfectly. I’ll keep trying to see if there’s a change in the syntax. Otherwise, I may post my script in the hope that you can see in a moment what’s going wrong. Thank you again.

How do I get 2.1?
When I click the download link above I seem to get version 2

Happy New Year and sorry for the inconvenience.

I recompiled the project against the latest SDK for Intel and Apple Silicon and code signed and notarized the app.
The minimum system version is macOS 10.15 Catalina.
To avoid further confusion the version of the app is 2.1

I ran also successfully some test scripts.

BonjourEvents_2.1

1 Like

@StefanK - Thank you for compiling the latest version. I’m sorry to ask your help, but I can’t figure out how to change a script that worked with version 1 so that it works with the current version. The bit of script that used to work is this:

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 tried changing scan type to scan for type - but the script breaks with browser finished. Is there something obvious that I’ve missed? Many thanks again.

I finally read your sample code above and tried this, but it won’t let me run it twice because it says the browser already exists. Is there a way to remove it, not just stop it?


tell application "Bonjour Events 2.1"
	-- create new browser instance, the name property is mandatory
	set AFPBrowser to make new browser with properties {name:"MyBrowser"}
	tell AFPBrowser
		scan for type "_ipp._tcp." in domain "local" time out after 10.0 -- default time out value is 5.0
	end tell
	(*
	
	alternatively set the properties in separate lines
	
		tell AFPBrowser
			set domain to "local" -- default is "" (any domain)
			set service type to "_afpovertcp._tcp"
			scan
		end tell
	*)
	
	set {scan error:scanError, timed out:timedOut, bonjour services:bonjourServices} to result
	if scanError is not missing value then
		-- do some error handling
	else if timedOut then
		-- handle time out
	else if (count bonjourServices) > 0 then
		set firstService to item 1 of bonjourServices
		tell firstService
			set ipv4Address to IPv4 address
			set theHost to host
		end tell
	end if
	-- Bonjour Events quits automatically after 5 minutes of inactivity
	
	-- this doesn't seem to work 
-- tell browser "MyBrowser" to stop
	
end tell


Version 2 allows to create multiple browsers. And waiting for completion is not necessary, the browser waits itself

try
	tell application "Bonjour Events"
		if (exists browser "MyBrowser") then
			set IPPBrowser to browser "MyBrowser"
		else
			set IPPBrowser to make new browser with properties {name:"MyBrowser"}
		end if
		tell IPPBrowser
			scan for type "_ipp._tcp" in domain "local"
			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
	end tell
on error errorMessage number errorNumber
	display dialog "An error occurred: " & errorMessage & " (" & errorNumber & ")"
end try

Alternatively delete the browser right before calling quit

try
	tell application "Bonjour Events"
		set IPPBrowser to make new browser with properties {name:"MyBrowser"}
		tell IPPBrowser
			scan for type "_ipp._tcp" in domain "local"
			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
			
			-- Bonjour Events quits automatically after 2 minutes of inactivity
		end tell
		delete IPPBrowser
		quit
	end tell
on error errorMessage number errorNumber
	display dialog "An error occurred: " & errorMessage & " (" & errorNumber & ")"
end try