system events and processes with the same name

I’m sure this has been answered before, but my searching abilities are not up to finding the solution.

How can one pass a System Events command via something other than the process name? I need to do this when there are multiple processes with the same name, and I would like them all to do the same action (imagine different versions of a piece of software, all of which use the same application process name).

I thought it might be possible to use the process IDs, but my abilities limit me to getting the proper applications to the front.

Here’s something simple to illustrate the problem—I’ve used the name of the application as “foo”, but you could duplicate any application on your hard driver, and then launch both the original and the copy. (TextEdit is fine)


tell application "System Events"
	set theIDs to the id of every process whose name is "foo"
	repeat with anID in theIDs
		set frontmost of process id anID to true
                --- need something like (or a workaround for)
		tell frontmost application to keystroke "hello, world"
	end repeat
end tell

Any tips would be helpful.

I don’t understand why you’re running two instances of TextEdit say, instead of two windows. Won’t the newer version of your software open the older file?

I’m not really running two instances of TextEdit… I had included it as an example to illustrate the problem.

I do, however, sometimes run multiple versions of my favorite statistical analysis software (Stata) at the same time. The application is not currently scriptable, so I’m using System Events to control it. Since the versions have the same process names, using a ‘tell application …’ block limits me to working with the version which has the smallest process id number. I would like a method of either sending the same events to all open versions or to be able to pick a version to receive the events.

I realize that this goes against ‘you should run only one instance of an application’ philosophy, but it does come in handy from time to time.

I can see this is very old, but in case anyone else comes across it trying to do this, here’s what I’ve done, where I’m running more than one copy of FileMaker Pro. I want my AppleScript to work with the frontmost FileMaker, not pick one randomly. But, if none is frontmost, it picks the first one it finds.

set appNameMatchString to "FileMaker"

tell application "System Events"
	set frontAppName to name of first application process whose frontmost is true
	set appProcID to id of first application process whose frontmost is true
	-- ^^^ we MUST get this HERE - we MUST NOT try to get a reference to the frontmost app, since the dereference will then talk to some OTHER app.
	if frontAppName does not contain appNameMatchString then
		-- frontmost does not match, so just get the 1st one we can find.
		-- (when using, you should probably tell it to set frontmost to true, to be sure)
		try
			set appProcID to id of first application process whose name contains appNameMatchString
		on error errMsg number errNum
			error errMsg number errNum
		end try
	end if
	
	
	tell process id appProcID
		return properties
		-- OR, do whatever you want here
	end tell
end tell

Obviously, I put this into a handler, and return the appProcID, then use that with a tell process id myProcID block in my main script.

FYI since macOS Catalina (10.15) you can no longer duplicate any default application ( TextEdit for example ). But,

open -n -a "Safari"

will open a second instance.

True. Also, some applications respond with the same process “name”, regardless of what the .app bundle is named. So, even if you duplicate and use a different bundle file name, System Events sees the same process name, so a tell application "Something" will not necessarily talk to the one you want. The frontmost might not be the one launched earliest.

The Cocoa class NSWorkspace may be used in an ASObjC script to configure and launch a new application instance and to retrieve the launched application’s process ID. The currently recommended NSWorkspace method, openApplicationAtURL:configuration:completionHandler:, unfortunately returns the process ID in a completion block that is inaccessible to ASObjC scripts. However, an older NSWorkspace method, launchApplicationAtURL:options:configuration:error, although deprecated, still works well and returns results that are accessible to ASObjC scripts.

The following example opens a new instance of Safari, retrieves the new instance’s process ID, then targets that instance in System Events:

use framework "Cocoa"
use scripting additions

set wsObj to current application's NSWorkspace's sharedWorkspace()
set appURLObj to wsObj's URLForApplicationWithBundleIdentifier:"com.apple.Safari"
set launchOptions to (get current application's NSWorkspaceLaunchNewInstance) + (get current application's NSWorkspaceLaunchWithoutActivation) -- configures launch options; see NSWorkspaceLaunchOptions in the Cocoa documentation for a complete list of launch options; the 'get'  command resolves the Cocoa references into their integer values
set configDictObj to current application's NSDictionary's dictionary() -- empty dictionary here; may be used to pass argument values to the newly launched application instance
set {launchedAppObj, errorObj} to (wsObj's launchApplicationAtURL:appURLObj options:launchOptions configuration:configDictObj |error|:(reference))
if errorObj ≠ missing value then
	set errorMessage to errorObj's |description|() as text
	error errorMessage
end if
set processID to launchedAppObj's processIdentifier() -- launchedAppObj is an NSRunningApplication instance; see NSRunningApplication in the Cocoa documentation for a complete list of retrievable application properties

tell application "System Events" to tell (first process whose unix id = processID)
	-- Perform actions on the process that was just launched
	set SEProcessID to its unix id
end tell

set sampleMessage to "Process ID of the application instance launched by NSWorkspace:" & linefeed & tab & processID & linefeed & linefeed & "Process ID of the application instance targeted by System Events:" & linefeed & tab & SEProcessID & linefeed & linefeed & "Note that System Events is targeting the application instance that was just launched."
activate
display dialog sampleMessage

Here’s a plain vanilla method.

set newApplicationInstanceId to Application_Open_Instance("Safari")
tell application "System Events"
	tell newApplicationInstanceId
		return properties
	end tell
end tell


on Application_Open_Instance(appName)
	tell application "System Events"
		set existingAppIds to id of every process whose name is appName
		do shell script "open -n -a \"" & appName & "\""
		set newAppId to id of every process whose name is appName
		repeat with thisID in newAppId
			if thisID is not in existingAppIds then
				return (a reference to application process id (contents of thisID))
				--return (contents of thisID)--if you only want the app process id
			end if
		end repeat
	end tell
end Application_Open_Instance
1 Like

If the shell open command is used to launch the new application instance, the shell pgrep command can perform the work of identifying the newly opened instance. pgrep returns the process IDs of processes matching the supplied pattern. The -n option confines the result to the newest matching process, which will ordinarily be the application instance just launched, and the -i option performs case-insensitive matching. A regular expression pattern in the form ^<app name>$ confines the match to a process having that exact name, without preceding or following characters. Because open may return before the new process has appeared in the process table, a brief delay is inserted before pgrep is invoked:

set newApplicationInstanceRef to Application_Open_Instance("Safari")
tell application "System Events"
	tell newApplicationInstanceRef
		return properties
	end tell
end tell

on Application_Open_Instance(appName)
	set pid to (do shell script "
		open -n -a " & appName's quoted form & "
		sleep 0.5
		pgrep -n -i \"^" & appName & "$\"
	") as integer
	tell application "System Events" to return (a reference to (first process whose unix id = pid))
end Application_Open_Instance