How to script an application using AppleScript, but only if installed? - applescript

A few of my scripts notify using Growl, but I'm trying to run them now on a machine that doesn't have Growl installed.
This line
tell application "Growl"
notify with name "Script" title "Script" description NotifyText application name "Script" with sticky
-- _
end tell
Gives this compiler error at the character underlined:
Expected “given”, “into”, “with”, “without”, other parameter name, etc. but found “"”.
How can an AppleScript optionally use an application?

To my knowledge Script Editor does not have a conditional compile directive and if an application is not installed then it will error out on compile. In some cases, Script Editor can be faked out into compiling by creating a dummy app in the name of the missing application but this has limited application and success and really depends on the code written for the missing application.
As to writing code to conditionally use a target application if it's installed, one needs to test for its existence and wrap the target applications code in a if statement block. In the following example AppleScript code, I have a test video file in my Movies folder that I want to play in VLC if it's installed, and if not then play in QuickTime Player, e.g.:
set thisVideo to (path to movies folder as string) & "test.mp4"
try
tell application "Finder" to get application file id "org.videolan.vlc"
set appExists to true
on error
set appExists to false
end try
if appExists then
set theApp to "VLC"
tell application theApp to open thisVideo
else
tell application "QuickTime Player"
open thisVideo
play document 1
end tell
end if
Now I do not have Growl installed but the example AppleScript code shown above should work for it as well. From a Mac that has it installed, you just need to ascertain its bundle identifier property while the target application is running and use it in place of what's in the example code, i.e.:
tell application "System Events" to get bundle identifier of application process "Growl"
Note: The example AppleScript code is just that and does not employ any other error handling then what's shown and is meant only to show one of many ways to accomplish a task. The onus is always upon the User to add/use appropriate error handling as needed/wanted.

Related

Bring application to front by its path

How can I bring an application to front by its path?
i.e. Assume /Applications/MyApp.app have already started; At a time I want to bring that MyApp.app window to front by passing its path to AppleScript: myApplScript.scpt /Applications/MyApp.app.
I tried by this script, but this did not worked for me:
on run argv
set apppath to (item 1 of argv) as string
tell application "System Events"
set frontmost of every process whose path is apppath to true
end tell
end run
Thanks!
Usually, all you need to do is to activate the application, which switches focus to it (even if the application is already running):
activate application "MyApp"
You can use this command directly with its path like this:
activate application "/Applications/MyApp.app"
or, in your specific case,
activate application apppath
although you shouldn't need to.
If that doesn't work, you can try System Events:
tell application "System Events" to set frontmost of process "MyApp" to true
or, using its path:
tell application "System Events" to set the frontmost of the first process ¬
whose POSIX path of application file is "/Applications/MyApp.app" to true
Have you tried checking for the open files for the process?
There are several ways of doing this (see: this article)
Normally the application binary will be an open file of the process.
Keep in mind that the binary executable may be contained in a package and you will need to look for it by doing a show package contents in finder and then appending the path to the executable in the package. In the package check under /Contents/MacOs/.
This might take a long time if you have many open processes and therefore not very practical to run often.
Also take into account that a single executable file can be running in more then one process instance.
A pity that the standard Activity Monitor is not scriptable with applescript which I just verified hoping for a more applescript friendly solution. You should still be able to achieve the same result via calling the shell command line.
Also often the process name is equal or similar to the filename but that depends on the application or process you're looking for.
I'm guessing you must have different versions of the same app installed that you need to check for the application by pathname?
Or if you don't need to figure out the path name dynamically you can simply look it up in advance using the Activity Monitor or on the command line. There you can identify the process name that corresponds to you your running application that was started from the specified path. Knowing the process name in advance then makes it easy and you can simply use
tell application "System Events" to get application process "Name of My Application"
--insert the actions you want to perform on your running app here
end tell
To solve any process uniqueness issues in case the same app is running more then once you can always result to the pid or unix process Id. Here's an article on how to get the pid in applescript of a running app

AppleScript (or alternative) that checks running apps and closes the last opened instance

I have an OS X app, let's call it TestOSX.app. This is its displayed name (taken from the Info.plist CFBundleName key as far as I can tell).
For a variety of reasons (it can be circumvented by copying the app to another place or by opening it from Terminal; it does not work if CFBundleExecutable is not the binary per-se but a script that sets up some stuff before launching the binary itself...), I cannot rely on OS X's built-in policy to block someone from starting a second instance of the app, nor can I use the LSMultipleInstancesProhibited key. But I do want to make sure that every second instance started by the same user is going to quit before being able to modify some resources. Different users should be able to run their own single instace of the app at the same time (this is why LSMultipleInstancesProhibited is no-go).
(I wanted to build a mechanism relying on flock(1) but it does not exist under OS X.)
So, the strategy is: when a user launches my app, first check whether an older version is already running; if there is, send this latest app instance (that the script has been executed "from") a request to quit, and bring the old instance to foreground.
I cannot use the name of the process per-se, as the app may use some embedded tools (like a proprietary updater) which will have a different name than the app itself. This is why something like this won't work:
tell application "System Events"
set listOfProcesses to (name of every process where background only is false)
end tell
, as the identified process may simply say updater (which is a part of the TestOSX bundle).
I have a snippet, probably parts of the "big thing", but it doesn't work as expected:
tell application "System Events"
set theProcess to first application process whose displayed name is "TestOSX"
set theOtherProcess to second application process whose displayed name is "TestOSX"
set frontmost of theOtherProcess to true
end tell
, this one always brings to front only the 1st app's process.
And I don't get it why it doesn't work as expected, as long as:
tell application "System events"
set listOfProcesses to (name of every process whose (dsiplayed name is "TestOSX"))
end tell
returns both instances. I guess somewhere the mapping between the process and the name is being lost.
[Edit]
Tried to modify the snippet above using:
tell application "System Events"
set theOtherProcess to id of second application process whose displayed name is "TestOSX"
set frontmost of theOtherProcess to true
end tell
, yet I get the error:
"Can't set frontmost of 680102 to true."
(This may be because I have a script that actually launches the binary, as said above?)
Okay, I came up with an ugly solution.
The bash script that is launched by double-clicking on the app icon is going to check how many instances of my app are running (with the aid of an AppleScript). If the answer is more than one, the bash script will end, thus my whole app terminating.
My launcher.command script:
#!/bin/bash
val=$(osascript ./instances_counter.scpt "TestOSX")
and the instances_counter.scpt, making use of the argument I passed:
on run argv
tell application "System Events"
set theProcessList to every application process whose displayed name is item 1 of argv
set noOfProcesses to count of theProcessList
end tell
return noOfProcesses
end run
That's it.

How to check in AppleScript if an app is running, without launching it - via osascript utility

Consider the following AppleScript:
on is_running(appName)
tell application "System Events" to (name of processes) contains appName
end is_running
set safRunning to is_running("Safari")
if safRunning then
tell application "Safari"
-- Stuff I only want executed if Safari is running goes here.
end tell
return "Running"
else
return "Not running"
end if
The problem: when I run this via the osascript command line utility, if Safari is not running, it gets launched and the script reports "Running". This is not the behaviour I desire or would expect. Note that it works as desired/expected when run within AppleScript Editor.
Is this an osascript bug / known issue? Or is it somehow intended behaviour for reasons I'm missing? Can anyone get it to work as desired? (BTW I'm running OSX 10.7.5; I can't see how to get osascript to report a version number).
If you comment out the tell / end tell lines, it behaves as I'd expect: if Safari is not running, it doesn't launch it, and prints "Not running". So it seems to me like the tell is what's causing Safari to be launched, but it doesn't need to be actually executed, just present in the script...? For a while I wondered if maybe this was just how tell is supposed to work, but since it doesn't work like this in AppleScript Editor, I guess not...
In fact, here's another, madder, version with similar behaviour:
on is_running(appName)
tell application "System Events" to (name of processes) contains appName
end is_running
set safRunning to is_running("Safari")
return safRunning
if false then
tell application "Safari"
end tell
end if
This still always launches Safari, even though tell is inside an if false block after the return statement! (But again, this is fine in AppleScript Editor.)
BTW, this behaviour isn't limited to Safari, but it also isn't universal:
Affected apps include: Safari, TextEdit, iPhoto, AppleScript Editor, iTerm, ...
Non-affected apps include: Google Chrome, iTunes, Preview, Mail, Terminal, Address Book, Echofon, ...
So, does anyone have any ideas about how I might fix or route around this? Is it an osascript bug? Or am I missing something about AppleScript's semantics?
For context: I'm trying to write a script (to be embedded/called from some python) which queries open browsers for the URLs of any tabs they have open; I've got it all working fine except that it always launches Safari, whether it's open or not. I've boiled down that undesirable behaviour to the simple test case shown above. I'm not aware of any way to run this script from python without using osascript, other than appscript, which I don't want to use because it's no longer developed/supported/recommended.
Many thanks for all inputs / insights!
I suspect the reason you are getting this is because each time you call the script from the command line with osascript the script is being compiled.
The act of compiling on a tell application will afaik make the app launch.
Calling the script from the command line with osascript from a pre-compiled file i.e .scpt does not cause this behaviour because the is no compiling to be done.
But calling it from a plain text (.txt,.sh ) file will so the app will launch.
If you do not want to use a .scpt file and want to use a plain text file then you could try the trick of putting a run script command in the applescript.
on is_running(appName)
tell application "System Events" to (name of processes) contains appName
end is_running
set safRunning to is_running("Safari")
if safRunning then
run script "tell application \"Safari\"
open location \"http://google.com\"
end tell"
return "Running"
else
return "Not running"
end if
The script in the run script is only compiled when needed. You will need to escape any characters like quotes as in my example.
It will be easier if you write the script in a normal applescript document first and compiled it to check for errors.
Then copy it to the plain text file.
UPDATE **
The method I used above was from a old script I had used to solved this issue a while before I answered here.
The answer works and is not trying to be elegant. ;-)
I actually like user1804762 method below. As it does work but feel the Answer is not clear enough so I will give an example on using it.
set appName to "Safari"
if application appName is running then
tell application id (id of application appName)
open location "http://google.com"
end tell
return "Running"
else
return "Not running"
end if
This script can be run from the command line with osascript
example:
osascript /Users/USERNAME/Desktop/foo.scpt
Notice that the script is saved as a compiled script. This will work ok and you can also save and use it as a plain text script.
i.e.
osascript /Users/USERNAME/Desktop/foo.applescript
Some Info:
"Enhanced Application Object Model":
tell application "iTunes"
if it is running then
pause
end if
end tell
You can also do it that way:
if application "iTunes" is running then
tell application "iTunes" to quit
end if
You can also do this:
get name of application "iTunes"
get version of application "iTunes"
And to complete the journey:
get id of application "TextEdit" --> "com.apple.TextEdit"
tell application id "com.apple.TextEdit"
make new document
end tell
That was the "Enhanced Application Object Model". If an app still launches (for example, the first time you compile & execute the script) I assume it is because AS has to get some info from the app which it did not found in the dictionary (or something like that...?).
OK, I know this question is really old, but I stumbled on it looking for a different issue and had to pipe in considering how complicated some of these responses are.
The simple code to achieve what you want(ed) is:
tell application "System Events"
if application process "Safari" exists then
-- do stuff you want to do only if Safari exists
end if
end tell
On older systems, the syntax used to be:
tell application "System Events"
if exists of application process "Safari" is true then
-- do stuff you want to do only if Safari exists
end if
end tell
One of these should definitely work for you, intrepid searcher of Applescript solutions for action only when an app is running.
Oh! Bonus tip: And if you're not sure what the application process name is exactly (it is usually but not always the app name), before coding your final script run…
tell application "System Events"
get every application process
end tell
And find your app process name in the results.
Here's a screen grab of running that command. (Note the zillions of Google Chrome Helper instances. Thanks Google!)
HTH!
tell application "Finder"
set applicationsnames to get the name of every process whose visible is true
end tell
set appName to "Safari"
if applicationsnames does not contain appName then
say (appName & " is not running")
--add here what you want to happen
end if
return applicationsnames
This is returning {"Finder", "JavaAppLauncher", "firefox", "Microsoft Word", "iTunes", "AppleScript Editor"} for me
Hope this helps
All the previously made answers suffer from the same issue, though:
They look for the app by its name. However, the user may rename the app, and then the script will believe the app does not run, when in fact it does.
To properly check for a running app, it should be found by its bundle ID, which the user cannot change.
The bundle ID can be inquired with this command, for instance, when the app is already running:
tell application "System Events"
get bundle identifier of application process "Safari"
end tell
Or like this for any installed app:
get id of application "Safari"
To check whether an app with a particular bundle ID is running, use this code:
tell application "System Events"
set ids to bundle identifier of every application process
if ids contains "com.apple.safari" then
return "Running"
else
return "Not running"
end if
end tell
Furthermore, here's an example to check if an app is running, then quit it, then relaunch it, ensuring that the very same app is relaunched that was running before, and not some other copy that may also exist:
set bundleID to "com.apple.safari"
set apps to runningApps(bundleID)
set appCount to length of apps
if appCount is not 0 then
quit application id bundleID
repeat while length of runningApps(bundleID) = appCount
-- wait for the app to quit
end repeat
open first item of apps
end if
on runningApps(bundleID)
-- The try block is to catch the rare case of having more than one
-- copy of an app running at the same time. Unfortunately, in that
-- case this code will not run as expected, because we don't get the
-- correct list of multiple items back then. But at least the script
-- will not crash from it but handle it gracefully.
tell application "System Events"
try
return application file of (every application process whose bundle identifier = bundleID)
end try
end tell
return {}
end runningApps
I had the same problem as described here trying to set up an AppleScript (triggered by a BetterTouchTool gesture) that plays/pauses VLC or iTunes, but only iTunes if VLC is not running (due to my workflow) and, naturally, only VLC while it's running. (I use the automatic pause/play trigger for iTunes in VLC's settings, for launch and quit of the app.)
VLC was always launched on the first use of the BetterTouchTool-trigger after every relaunch of BTT as the dictionary-cache is deleted at that point and the AppleScript handler has to launch every scripted application if a tell is aimed at it in order to call its dictionary.
I didn't find anything that avoided this anywhere; there were some attempts, but none worked for me as the dictionary-call by the script handler is nothing we can influence. I came up with this dirty workaround:
Create a separate AppleScript file only containing the line that includes the tell for VLC
Save it at some place where it won't annoy you
Replace the line containing the tell in the original AppleScript with a line that runs that script
This will lead to the first compilation of the script not calling the application (VLC, in my case) directly, only the script, which means that the application will not need to launch.
VLC will need to launch once that separate file is called, but, well, if you call that file in order to tell VLC something, you will have VLC already opened (or will want it open) anyway.
The AppleScript I call through my BetterTouchTool-trigger (a specific tap on the trackpad, in my case) looks like this:
if application "iTunes" is running and not application "VLC" is running then
tell application "iTunes" to playpause
end if
if application "VLC" is running then
run script "/Users/jannis/bin/PlayVLC.scpt"
end if
The separate AppleScript file ("PLayVLC.scpt, saved in a folder called "bin" in my user folder which I created manually ages ago for such purposes) is just this:
tell application "VLC" to play
If you open that script manually, it will of course also launch VLC. But that hopefully won't be necessary often, or ever.
I actually have no idea if this creates any deeper problems I don't know of as I'm not a pro coder; if so, please notify me. I hope this helps anyone!

Send all files on Desktop to Evernote then delete

Good morning,
I am trying to write an AppleScript that I can run that will send all the files on my desktop to Evernote, and then delete the files. My code to date is:
on run {input}
tell application "Finder"
select every file of desktop
end tell
tell application "Evernote"
repeat with SelectedFile in input
try
create note from file SelectedFile notebook "Auto Import"
end try
end repeat
end tell
tell application "Finder"
delete every file of desktop
end tell
end run
If I run this then the first and last 'tell' work fine (ie. the script highlights then deletes all the files on the desktop), but the middle 'tell' doesn't do anything.
However, if I manually highlight all the files on the desktop and then run just the middle 'tell' then it imports fine - each item into a separate note as intended.
As you can tell, I am new to AppleScript - I suspect I need to put the selected files in an array of some sort, but can't figure it out. Help!
Many thanks
Rich
Your code fails because there is no relation between your input variable and the selection of files via Finder – which means that your list is empty, and Evernote is not processing anything at all. You have obfuscated the problem by wrapping the Evernote import command in a try block without any error processing, which means all errors just go unnoticed (to avoid this kind of problem, it is good practice to always log the error message in an on error clause, if nothing else).
Also, you don’t actually need to select files on the Desktop via AppleScript to process them. The following code will grab all visible files (excluding pseudo-files like packages / apps):
tell application "System Events"
set desktopFiles to every disk item of (desktop folder of user domain) whose visible is true and class is file
end tell
Pass the list you retrieved that way to Evernote for processing:
repeat with aFile in desktopFiles as list
try
tell application "Evernote" to create note from file (aFile as alias) notebook "Auto Import"
tell application "System Events" to delete aFile
on error errorMessage
log errorMessage
end try
end repeat
and you are good to go.
Note that by judiciously placing the deletion command (right after the import command, inside the try block, inside the loop over all files), you make sure it is only called if Evernote does not error on import while avoiding having to iterate over the files several times.
A final note: you don’t have to use the block syntax for tell statements if there is only one command to execute – using tell <target> to <command> is easier and will keep you out of nested context hell.
Thanks #adayzone for corrections on list handling and alias coercion
Try
tell application "System Events" to set xxx to get every file of (desktop folder of user domain) whose visible is true
repeat with i from 1 to count of xxx
set SelectedFile to item i of xxx as alias
try
tell application "Evernote" to create note from file SelectedFile notebook "Auto Import"
tell application "Finder" to delete SelectedFile
end try
end repeat
Thanks #fanaugen

Create AppleScript for a program that isn't installed on the current computer

I'm trying to make two copies of an AppleScript, one that works for Entourage and one for out Outlook. I only have Entourage installed on the current computer.
According to the info on Microsoft's site, both applications have the same library of AppleScript commands, and I should be able to simply change the application name referenced within the script.
Changing:
Tell application "Microsoft Entourage"
to
Tell application "Microsoft Outlook"
Prevents me from saving the script because I don't have outlook installed on this computer. Is there any way around this? Do I need to use a text editor to edit the actual script file and change it there?
Thanks!
The following work-around may do the trick. On the computer where Entourage is installed, a using terms directive will let you compile the script, even if Outlook is not installed:
set theApp to a reference to application "Microsoft Outlook"
using terms from application "Microsoft Entourage"
tell theApp
get version
...
end tell
end using terms from
Upon compiling and saving the script the AppleScript Editor will bug you about the missing Outlook application, but it will nevertheless produce a compiled AppleScript file (.scpt).
Applescript is a pre-complied file format, meaning that every time you click "Save" it runs through a series of steps to ensure the script will work, but just short of actually running through the script's logic. Part of those steps is to look for the application to see if it exists on the Mac.
In short, if you want to save the script as an Applescript, you need the target application installed, otherwise you can save the script as a text file and move the file over to the target Mac to save as an Applescript over there.
It should be possible to make one script that works with both Entourage and Outlook, without bugging you if one isn't found either when you compile or when you run. I don't have either Entourage or Outlook but it should work like this:
using terms from application "Microsoft Entourage"
script theScript
tell application "Finder" to try
set theApp to application file id "Entourage's Bundle ID" as text
on error
set theApp to application file id "Outlook's Bundle ID" as text
end try
tell application theApp
-- do stuff
end tell
end script
end using terms from
store script theScript in "MyScript.scpt"
"using terms from" is only relevant when compiling the script - it isn't needed when running, though for some reason you'll still get bugged if that app isn't found. So by wrapping it around a script object and then writing out that script to file, the resultant script will still run but won't contain "using terms from" and so won't bug the user.
For getting a reference to the right app, Finder can look for it by ID and simply error if it isn't found rather than bugging the user. You'll need to insert the proper ID's there, I don't know what they are.

Resources