Running AppleScripts through the ScriptMonitor.app Utility - macos

When AppleScripts are run through the system-wide Script Menu, their progress is displayed in the menu bar using the ScriptMonitor applet (located in /System/Library/CoreServices/ScriptMonitor.app, introduced in OS X Yosemite). This allows you to stay aware of running scripts, monitor progress, and easily stop running scripts.
Is it possible to run AppleScripts through the ScriptMonitor from outside the Script Menu, for example from Terminal or from system calls from other applications?
I have tried various permutations of the following commands, all without success:
/System/Library/CoreServices/ScriptMonitor.app/Contents/MacOS/ScriptMonitor /PATH/TO/SCRIPT
or
open -a /System/Library/CoreServices/ScriptMonitor.app --args /PATH/TO/SCRIPT
The reason this would be useful is that there are many helper applications that run AppleScripts in response to events, but tend not to be very good at notifying the user about their success or failure.

So, it turns out this can be done using NSUserScriptTask from the Cocoa frameworks, either as part of a compiled command-line application or through AppleScript/Objective-C (ASObjC).
This solution allows AppleScripts, Automator workflows, and shell scripts to be run from the System ScriptMonitor.app utility.
ASObjC Solution
This handler will run natively on OS X 10.10 Yosemite and later. It takes a single parameter, a string containing the POSIX-style (slash-delimited) path to the script. The script is executed immediately in the background, and no result is returned.
use framework "Foundation"
to runInScriptMonitor(script_path)
set {script_task, url_error} to current application's NSUserScriptTask's alloc()'s initWithURL:(script_path as POSIX file) |error|:(reference)
if url_error is not missing value then error (url_error's localizedDescription as string) number (url_error's code as integer)
script_task's executeWithCompletionHandler:(missing value)
# The following delay was increased due to a system hang on Mojave after installation of Security Update 2020-004 (previously, the delay was 0.05).
delay 10 -- Necessary to allow NSUserScriptTask to be dispatched before execution terminates.
return
end runInScriptMonitor
It is called as follows: runInScriptMonitor("/PATH/TO/FILE")
This allows you to run scripts in ScriptMonitor from within AppleScript. If the call is placed in a wrapper AppleScript, the wrapper script can then be called from the command line using osascript.
Compiled Objective-C Solution
Follow these instructions to create a command-line program that takes the script path as input and runs the script using ScriptMonitor. You must have the Xcode Command Line Tools (or the full Xcode) installed in order to compile the code.
Save the following code as osascriptmonitor.m in the Desktop folder:
#import <Foundation/Foundation.h>
int main(int argc, const char *argv[]) {
if (argc < 2) {
printf("usage: osascriptmonitor /path/to/script\n");
} else {
NSString *script_path = [NSString stringWithUTF8String:argv[1]];
NSUserScriptTask *script_task = [[NSUserScriptTask alloc] initWithURL:[NSURL fileURLWithPath:script_path] error:nil];
[script_task executeWithCompletionHandler:nil];
[NSThread sleepForTimeInterval:60.0f];
}
return 0;
}
Compile the program by running the following commands from Terminal:
cd ~/Desktop
gcc -framework Foundation osascriptmonitor.m -o osascriptmonitor
You will now have an executable file named osascriptmonitor on your Desktop. You can run that program from Terminal, passing the path of the script that you want to run in ScriptMonitor.
Example (replace /PATH/TO/SCRIPT with the path of the script you want to run):
~/Desktop/osascriptmonitor "/PATH/TO/SCRIPT"
If you then move the executable file to /usr/local/bin, you can run the program without having to specify its entire path.
osascriptmonitor "/PATH/TO/SCRIPT"
Edit Jan 3, 2020:
Direct Solution
By happy accident, I stumbled across an undocumented option for osascript that makes the above largely unnecessary for AppleScripts: the -P switch.
Usage: osascript -P "/PATH/TO/SCRIPT"
On its own, this will make the script appear in the menu only if Script Monitor is already running. Script Monitor can be launched ahead of time (or while the script is running) and will quit automatically once all scripts have finished.
The best way to launch Script Monitor is using the -g option:
open -g /System/Library/CoreServices/ScriptMonitor.app
Using this method, it is possible to easily pass arguments to the script in addition to having it appear in Script Monitor.

Related

Bash shell is locked after running script

I made my own script to make testing a little faster to execute on my .NET projects.
The name of the script is TestCoverage and is located in /home/user/bin folder with executable permissions and also added to my PATH for convenience.
#!/bin/bash
echo "Collecting TestCoverage..."
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
The script runs as expected but after it's done the terminal stops taking input. I can only press the Enter key and the cursor is blinking as usual.
Bash version 4.4.20 running on Ubuntu 18.04
I've made some testing and it seems to take input but it isn't showed to the console. The problem is not consistent, it comes and goes through different terminal sessions.
Your script (and likely the dotnet process it invokes, because echo is a shell intrinsic that usually returns within milliseconds) is probably still running and hasn't returned.
You can open another console and type ps aux -H to view the running processes - look out for some adjacent lines with TestCoverage, bash, and dotnet.
In those lines, you can see what state the process(es) are in
(RTM for further detail).
If you are running a console under X, you can first look around whether you accidentally pushed some running dotnet test ... window behind your console window. :-)

Detect whether macOS app was launched from the command line (Terminal)

I have a GUI macOS app that may also be launched from Terminal, with optional command line arguments.
When launched with arguments, I like to run the app in a "cmdline" mode where I do not display any UI but instead communicate via stdin + stdout only.
I can detect this cmdline mode like this:
BOOL cmdMode = NSProcessInfo.processInfo.arguments.count > 1;
(arg 0 is always the executable's path, so any more args would be manually passed args).
Now, here's the big question:
If the user invokes my app without arguments from Terminal (by invoking the app's executable in Contents/MacOS, i.e. not via the open cmd), I like to also go into the cmdline mode. How do I detect this?
Note: Older OS X versions did pass a "-psn ..." argument that, when not present, could be used to detect a launch from cmdline, but recent macOS versions seem to not pass this argument any more when launching apps from the Finder, so I cannot use that for detection any more.
Update
I realize that I can almost correctly solve this by checking for the presence of certain environment variables:
TERM and PWD are only set when launching the app from Terminal but not from Finder.
However, I also like to be able to tell the difference between being launched directly (executable in Contents/MacOS dir) vs. launched with the open command as I consider the open cmd being equivalent to opening the app via Finder or from another app via Launch Services.
In short, the question might also be: Detect whether an app was launched by Launch Services
For the record, here are the values from environ(). The ones marked with an asterisk are only present when invoked from Terminal.app but not when lanched from Finder:
__CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0
* _=/Applications/Myapp.app/Contents/MacOS/Myapp
Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.laVQnD7IXl/Render
HOME=/Users/username
* LANG=en_US.UTF-8
* LC_ALL=en_US.UTF-8
* LC_CTYPE=UTF-8
LOGNAME=username
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
* PWD=/Users/username
SHELL=/bin/bash
* SHLVL=1
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.KeHv8KNuuk/Listeners
* TERM_PROGRAM_VERSION=388.1.2
* TERM_PROGRAM=Apple_Terminal
* TERM_SESSION_ID=EF2C59E6-D661-45BE-B7EF-7A0E71158C8D
* TERM=xterm-color
TMPDIR=/var/folders/hm/ycnxcbwx8xl1v7008k8wnpjh0000gn/T/
USER=username
XPC_FLAGS=0x0
XPC_SERVICE_NAME=0
There are, however, no envinment values that are unique to apps launched with Launch Services (such as when double clicked in Finder).
If you want to know what process has executed your program, you could use getppid() to get the parent process ID, then inspect that process to determine whether you were executed by an interactive shell process, or Finder, or launchctl, etc.
/sbin/launchd is PID 1 - if your process's parent PID is 1, you were executed by launchd.
Otherwise, you were executed by another process - probably an interactive shell, or as a subprocess of another process. You can use the KERN_PROCARGS syscall with sysctl() to get the process name by its PID.
You might also want to consider using isatty(STDIN) as well: interactive shells have a TTY, non-interactive shells and other processes won't.

AppleScript: execution error -10810 when launching certain applications from shebang'ed scripts

I'm running OS X 10.10.2. I'm facing a weird issue where AppleScript won't launch applications from shebang'ed scripts while working fine everywhere else (Script Editor, piping to osascript, etc.). Specifically, consider the following example script named launch-app:
#!/usr/bin/osascript
launch application "TextEdit"
When TextEdit is not running and I do
./launch-app
I get
./launch-app:0:29: execution error: An error of type -10810 has occurred. (-10810)
When I do
<launch-app osascript
Well, it works just fine; which means the following Bash script will also work:
#!/usr/bin/env bash
osascript <<EOF
launch application "TextEdit"
EOF
Really weird. (By the way, a tell ... activate ... end tell block results in the same error. I'm using launch here just to keep to example minimal.)
I have some old scripts that involve activating an application (well, practically all my old scripts involve tell ... activate ... end tell) that definitely worked in the past. I can't tell when things began to fall apart because when I run those scripts, most often the applications to activate are already launched. I have the impression that the issue dates back at least to 10.10.1.
I have looked at several related posts here on SO, e.g., this one, but they don't help. I also tried to understand error -10810 by reading articles like this one, but my problem definitely doesn't look like a filled process table (otherwise why does directly calling osascript works while running osascript from a shebang doesn't?).
Update: The bug has been fixed in OSX 10.10.3.
Just to provide a state-of-the-union post:
The behavior observed is a bug in OSX 10.10 still unresolved as of OSX 10.10.2 (as of 10 Mar 2015):
Anyone interested in getting this fixed should file their own bug at http://bugreport.apple.com.
It applies to executable scripts that are directly or indirectly passed to osascript - whether:
explicitly (osascript launch-app)
or implicitly, via the shebang line, by direct invocation (./launch-app)
The specific form of the shebang line is irrelevant (whether #!/usr/bin/osascript or #!/usr/bin/env osascript or #!/usr/bin/env osascript -l JavaScript or ...), what matters is whether the file has the executable bit (permission) set (e.g., via chmod +x).
Workarounds:
As suggested by the OP, feed the file to osascript via stdin: osascript < launch-app
This has side effects; for instance, name of me will report msng instead of the name of the script.
Remove the executable bit from the script and invoke it explicitly with osascript:
chmod -x launch-app # a one-time operation
osascript launch-app # with the executable bit unset, this should work
Looking at the man page for osascript, when you send lines of applescript code you should put the "-e" option infront of each separate line.
So here's what I tested. I made a bash script with the -e option...
#!/bin/bash
osascript -e 'launch application "TextEdit"'
And one without.
#!/bin/bash
osascript 'launch application "TextEdit"'
The one without the -e option does not run. As such I think this could be a cause of your problem... there's no -e option in your code.
Note that I tested your code too and got the same error as you. There's a command line utility "/usr/bin/macerror" and I entered your error code into that. Here's the result.
Unknown error (-10810) at /usr/bin/macerror5.18 line 40, <DATA> line 1.
Good luck.
There is no need for using osascript to launch applications. There is a built in command line utility named open, that will open your app from the terminal commandline, or a shebanged script. For doucumentation, enter "man open" in a terminal window. It is a really nifty utility, with a lot of options. :)
The open utility, will lauch applications that are not running, but I also wonder out of curiosity: have you tried "tell application appname to run", or just "tell application appname to activate"?
The osascript below, works for me, on 10.9
#!/usr/bin/osascript
tell application "TextEdit" to launch
I guess you'll have to commmand the app to do something, and not just try to "launch" it. Maybe "tell me to launch application appname also works".
Edit
I prefer to use open -b "com.apple.textedit", because then I also get the front window of textEdit, brought to front.
By the way, with the open -e command, you can open a textfile directly into TextEdit from the commandline. open is not totally as good as the plumb utility of plan-9, but it is really nifty.

Start script on login

I have written a ruby daemon and I would like for it to run when I log in. It is normally run by going to the command line and calling ruby my_ruby_script.rb. How can I start my daemon on login? (Running 10.6 Snow Leopard).
There's an option to add applications etc that need to start at login, you could try writing a shell script or an apple script thing that launches terminal and runs ruby my_ruby_script.rb, or possibly even just add my_ruby_script.rb to this list after adding a #!/bin/env ruby line to the top of that file. http://support.apple.com/kb/HT2602?viewlocale=en_US gives precise instructions as to how to add an application to be started at login.
If you need to use AppleScript to actually start a terminal application (I believe this is not the case, but I am not in front of my mac now and hence can't test), just create an applescript file with something like
do shell script "ruby <path>/my_ruby_script.rb"
Hope this helps
As Panda said, add:
#!/bin/env ruby
to the begining of the file, and then you could add a reference to your file inside ~/.bashrc or ~/.profile or even /etc/profile , depending on your needs.
Check this out: https://stackoverflow.com/questions/3484429/profile-and-bashrc-doesnt-work-on-my-mac/3484472#3484472

How to make OSX application that just runs opens some file type and runs arbitrary Ruby code?

It's trivial to make a program executable from shell - just put #!/usr/bin/ruby on top, chmod +x it and done. Unfortunately OSX won't let me associate file type with such scripts - it requires its .apps instead. This sort of distinction doesn't seem to exist on other operating systems.
What's the simplest way of making such .app, which would merely execute some arbitrary Ruby code?
You could use Automator or create an AppleScript application using "do shell script" to execute your Ruby script.
It seems that Script Editor has "Save as Application Bundle" option, which creates foo.app which I can associate with file types.

Resources