Applescript to ping test each client prior to ssh connection - macos

I'm trying to make an Applescript that connects to a list local ssh machines, with each connection opening in a new terminal window. Prior to attempting the ssh connection, I'd like to ping the client to see if it's available: if it is, run the ssh command, if not then iterates to the next client. When I run the script it seems to work for the first connection but then gives me --> error number -10004 for the remaining clients (and hangs the debugger). Any feedback would be greatly appreciated, thanks!
set hosts to {"10.2.0.199", "10.2.0.11", "10.2.0.91", "10.2.1.591", "10.2.0.41"}
set uname to {"asus_client01", "asrock_comp", "msi003", "gigabyte4", "intel05client"}
tell application "Terminal"
activate
repeat with i from 1 to the count of hosts
set this_uname to item i of uname --extract individual username
set this_host to item i of hosts as string --extract iPv4
set uname_host to this_uname & "#" & this_host
set hostUp to true
try
do shell script "ping -c 1 -t 5 " & this_host
on error
set hostUp to false
display dialog this_host & " seems to be down."
delay 2
end try
if hostUp then
do shell script "ssh " & uname_host
end if
end repeat
end tell

There is a difference between do shell script and do script. The difference is that do shell script is part of the standard script addition and will open an non-interactive shell, execute the given string, and return stdout back to you without any help from another application like Terminal. do shell script should never been used in any other tell application block except itself (me) because you violate some AppleScript securities you can find in AppleScript release and technical notes. do script command is part of AppleScript command in the application Terminal. do script will enter the given string in the targeted window and execute that like you have typed in Terminal yourself. do script is only supported by Terminal application and can't be used outside of it.
So it's either do shell script:
do shell script "ping -o stackoverflow.com
or do script by using the Terminal
tell application "Terminal"
do script "ping -o stackoverflow.com"
end tell
So the total script could look something like this:
set hosts to {"10.2.0.199", "10.2.0.11", "10.2.0.91", "10.2.1.591", "10.2.0.41"}
set uname to {"asus_client01", "asrock_comp", "msi003", "gigabyte4", "intel05client"}
--security check: hosts list can't be longer than uname
if (count of hosts) > (count of uname) then return
repeat with i from 1 to count hosts
repeat 1 times -- simulate continue
set currentAddress to item i of hosts
set currentHostname to item i of uname
if not ((do shell script "ping -o -t 5 " & currentAddress & "&>dev/null && echo yes || echo no") as boolean) then
exit repeat -- continue
end if
tell application "Terminal"
do script "ssh " & currentHostname
end tell
end repeat
end repeat

Related

Using bash for loops in conjunction with embedded applescript for bash scripts

I'm trying to get applescript to interact with the bash for loop that is a part of my code to keep from having to list each host manually and execute individual tell/end tell blocks for each host found in the hosts2.txt file.
The purpose of the script is to open a new terminal tab on my Mac and automatically launch "screen -r $HOST" in each new terminal until the end of the list of hosts in the hosts2.txt document. Each host is listed on its own line.
I've tried an all inclusive for loop, without the applescript "repeat 2 times" "end repeat" code that is shown below. It is repeating 2 times because there are only 2 hosts listed in the text document for testing purposes. Each time I have error output.
#!/bin/bash
for HOST in `cat ~/bin/hosts2.txt`
do echo $HOST
osascript -e 'repeat 2 times
tell application "Terminal" activate
tell application "System Events" to keystroke "t" using [command down]
tell application "Terminal" to activate
set host to $HOST
tell application "Terminal"
do shell script "screen -r " & host in front window
end tell
end repeat'
done
What I expect to happen is for the code the execute opening new terminal tabs with screen -r for each host. Error output is below this line.
dev
44:52: syntax error: Expected end of line but found command name. (-2741)
pulsar
44:52: syntax error: Expected end of line but found command name. (-2741)
There are a few issues with your script: some that stop it from working entirely; some that get it to do the wrong thing; and some that needn't have been there in the first place. There are couple of other answers that address some points, but neither of them appeared to test the script because there's a lot they don't address.
...for each host found in the hosts2.txt file...
...Each host is listed on its own line.
Then this line:
for HOST in `cat ~/bin/hosts2.txt`
is not what you want. That will create an array out of individual words, not lines in the file. You want to use the read command, that reads a file line-by-line. You can structure a loop in this manner:
while read -r HOST; do
.
.
.
done < ~/bin/hosts2.txt
As #entraz has already pointed out, your use of single quotes will stop shell variables from being expanding within your osascript.
Then there is the AppleScript itself.
I'm unclear why you included a repeat loop.
The purpose of the script is to open a new terminal tab on my Mac and automatically launch "screen -r $HOST" in each new terminal until the end of the list of hosts in the hosts2.txt document. Each host is listed on its own line.
It is repeating 2 times because there are only 2 hosts listed in the text document
This makes no sense, given that you implemented a bash loop in order to read the lines into the $HOST variable. Granted, you were reading words, not lines, but the AppleScript repeat is a head-scratcher. Bin it.
Then you have this:
tell application "Terminal" activate
tell application "System Events" to keystroke "t" using [command down]
tell application "Terminal" to activate
That's approximately infinity times the number you need to tell Terminal to activate.
This line:
set host to $HOST
will throw an error for two reasons: firstly, host is taken as an existing name of a property in AppleScript's standard additions, so you can't go and set it to a new value; secondly, there are no quotes around $HOST, so it's not going to be recognised as a string. But, this is just for your learning, as we're actually going to get rid of that line completely.
Finally:
tell application "Terminal"
do shell script "screen -r " & host in front window
end tell
is wrong. do shell script is not a Terminal command. It's a command belonging to AppleScript's standard additions. Therefore, if the rest of your code worked, and it got to this command, Terminal would execute nothing. Instead, the shell scripts would run in the background without an actual shell, so that's not much good to you.
The command you're after is do script.
Sadly, it does appear that, in High Sierra at least, the AppleScript commands to make new tabs and windows in Terminal no longer work, so I can see why you resorted to System Events to create a tab in the way that you have. Thankfully, that's not necessary, and nor are your multiple activate commands: do script will automatically run Terminal and execute a script in a new tab by default.
Therefore, the only AppleScript command you need is this:
tell application "Terminal" to do script "screen -r $HOST"
The Final Script
Putting this all together, here is the final hybrid script:
while read -r HOST; do
echo "$HOST"
osascript -e "tell application \"Terminal\" to do script \"screen -r $HOST\""
done < ~/bin/hosts2.txt
Alternatively
If you wanted to take the loop from bash and put it in AppleScript instead, you can do so like this, for which I'll use a heredoc (<<) to simplify the use of quotes and aid readability:
osascript <<OSA
property home : system attribute "HOME"
property file : POSIX file (home & "/bin/hosts2.txt")
set hosts to read my file using delimiter {return, linefeed}
repeat with host in hosts
tell application "Terminal" to do script ("screen -r " & host)
end repeat
OSA
You have a typo in your code.
The line tell application "Terminal" activate should be tell application "Terminal" to activate.
Variable expansion also doesn't work in single quotes in bash (single quotes means everything is interpreted literally), so the line set host to $HOST within the single quotes won't work.
Try this:
#!/bin/bash
for HOST in `cat ~/bin/hosts2.txt`
do echo $HOST
osascript -e "repeat 2 times
tell application \"Terminal\" to activate
tell application \"System Events\" to keystroke \"t\" using [command down]
tell application \"Terminal\" to activate
set host to \"$HOST\"
tell application \"Terminal\"
do shell script \"screen -r \" & host in front window
end tell
end repeat"
done
Edit: I think there's actually another problem: when setting a variable to a string in applescript, the string needs to be enclosed in quotes. So set host to $HOST causes an error because it interprets the value of $HOST ("pulsar" or "dev") as a command that it's unable to find/execute; it needs to be set host to \"$HOST\" instead. I've changed it above.
You might find a here-doc to be readable and easy to work with. Also, use a while-read loop to iterate over the lines of a file (ref: http://mywiki.wooledge.org/BashFAQ/001)
while read -r host; do
echo "$host"
osabody=$(cat << END_OSA
repeat 2 times
tell application "Terminal" to activate
tell application "System Events" to keystroke "t" using [command down]
tell application "Terminal" to activate
set host to "$host"
tell application "Terminal"
do shell script "screen -r " & host in front window
end tell
end repeat
END_OSA
)
osascript -e "$osabody"
done < ~/bin/hosts2.txt
The ending parenthesis of the $(cat ... command substitution has to be on a separate line because the terminating word of the heredoc must be the only characters on that line.

Tell an AppleScript to Restart Itself

I have an AppleScript saved as an application. When first run, it asks the user if they want to move it to the Applications folder. What I would like to be able to do is, after it's been moved, have the script quit itself and then reopen.
Obviously I can't say
tell me to quit
tell me to activate
...because it would stop running after the quit command.
Any suggestions?
Just run the script from inside the script, and make sure to terminate the current running of it with a return (can skip the actual return command if it's the last line of the script
-- do stuff
display dialog "Here I am again"
-- set alias to the script
-- run the script
set myScript to path to me
run script myScript
-- end current iteration
return
You can break out of this script by canceling the dialog, but you'll probably want to set a condition to check whether to run the script again.
Here's how I'd do this. Basically, You check if the application is running from the Applications folder. If it isn't, move it there, open another instance, and quit. Seems to work flawlessly. The activate in the beginning is because it seems that the application doesn't always move itself to the foreground:
--incase the application doesn't do this automagically
activate
set my_path to POSIX path of (path to me)
if my_path does not start with "/Applications/" then
set new_path to "/Applications/" & quoted form of (my name & ".app")
--"mv" wont move the application into the new location if it exists
try
do shell script "rm -rf " & new_path
end try
do shell script "mv -f " & quoted form of my_path & " " & new_path
do shell script "open -n " & new_path & " &> /dev/null &"
quit
end if
What I am doing.
First I enabled at by running the following command in Terminal (this only has to be done once)
sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.atrun.plist
then I have the following script
display dialog "running"
set mypath to POSIX path of (path to me)
set lun to open for access POSIX file "/tmp/springboard" with write permission
write "open " & mypath & linefeed to lun
close access lun
do shell script "at -f /tmp/springboard +1 minute"
quit

AppleScript ping shell script always returns true

I'm trying to make this AppleScript alert me whenever a host comes on or offline. All works fine if I change the do shell script line to set connected to true/false, so I know the rest of the code works. But the do shell script line seems to always return true. When I run it in terminal it works fine, but for some reason in the AppleScript it doesn't. Even if I set the host to a random IP address that returns false every time in terminal, do shell script returns true every time. I got the shell script from this answer.
on run
set oldconnected to false
repeat
set connected to do shell script "ping -o -t 5 My-Host.local >/dev/null && echo yes || echo no" as boolean
if connected and not oldconnected then
display notification "Device has connected"
end if
if not connected and oldconnected then
display notification "Device has disconnected"
end if
set oldconnected to connected
delay 5
end repeat
end run
You're missing some brackets around the do call. Replace:
set connected to do shell script "ping -o -t 5 My-Host.local >/dev/null && echo yes || echo no" as boolean
with:
set connected to (do shell script "ping -o -t 5 My-Host.local >/dev/null && echo yes || echo no") as boolean
and you should be good to go!

End Service if already running on Mac

I'm trying to create an Automator service that allows me to speak selected text.
I want to be able to use a keyboard shortcut, however, I also want to use a keyboard shortcut to end the service before it finishes.
I cannot figure out how to make the service stop once started running.
Here is my applescript code in Automator:
on run {input, parameters}
say input using "Alex" speaking rate 400
return input
end run
I know you can speak text in system prefs. But it maxes out at 300 wpm. I need this to do more than 300. Hence the Automator service.
Thanks for your help.
Another way should be to start saying nothing.
say "" stopping current speech true
If executed it will stop the current say output.
It's possible by getting the pid of the service, just write the pid to a temporary file.
When you want to stop the speaking, the script get the pid in the temporary file to quit this process ID.
on run {input, parameters}
set tFile to POSIX path of (path to temporary items as text) & "__auto_Runner_Speak_text__last_Pid__.txt"
try
set lastPid to word 1 of (read tFile) -- get the pid of the last speak text
if lastPid is not "" then
set tPids to words of (do shell script "/bin/ps -Axcro pid,command | sed -n '/Automator Runner/s/^ \\([0-9]*\\).*/\\1/p'")
if lastPid is in tPids then
do shell script "echo '' > " & (quoted form of tFile) & ";/bin/kill " & lastPid & " > /dev/null 2>&1 &" -- empty the file and stop the speaking
return -- quit this service
end if
end if
end try
do shell script "echo $PPID > " & quoted form of tFile -- write the PID of this workflow to the temp file
say input using "Alex" speaking rate 400
do shell script "echo '' > " & quoted form of tFile -- remove the PID of this workflow in the temp file
end run
Basically you'll have to kill the speech synthesis process...
try
set thePID to word 1 of (do shell script "/bin/ps -Axcro pid,command | grep speechsynthesis")
do shell script "kill -15 " & thePID
end try

Applescript Execute Shell with Input and Admin Privileges

I'm trying to write an automator service to fire up virtualhost.sh in a terminal.
Using the Services context menu the dialog opens to ask for the name of virtual host, then runs an applescript to launch terminal and pass in the input text.
What I want is to pass in my username and password to admin privileges so that I don't need to pass it in the terminal with sudo.
This can be done with do shell script but that executes a bin/sh and the virtualhost.sh is a bash script so I get the error bin/sh: virtualhost.sh command not found
Alternately I can use do script with command but this doesn't allow me to pass in the user name and password.
My code looks like so:
on run {input, parameters}
set vhost to "virtualhost.sh " & input
tell application "Terminal"
activate
do shell script vhost user name "user" password "pass" with
administrator privileges
end tell
end run
This produces the bin/sh error previously mentioned.
With do script with command
on run {input, parameters}
set vhost to "virtualhost.sh " & input
tell application "Terminal"
activate
do script with command vhost user name "user" password "pass" with
administrator privileges
end tell
end run
This produces an escaping error: Expected end of line, etc. but found property.
Is there a way to do this correctly?
Not specifically familiar with AppleScript Studio, but you can do it in plain old AppleScript (which appears to have the same issue) if you provide a full path to virtualhost.sh. (Also, Terminal is not required with "do shell script".) Example:
set vhost to "/usr/local/bin/virtualhost.sh " & input
do shell script vhost user name "user" password "pass" ¬
with administrator privileges
You can also extend $PATH (which is by default /usr/bin:/bin:/usr/sbin:/sbin with "do shell script") to include the path to virtualhost.sh, e.g.:
set vhost to "{ PATH=$PATH:/usr/local/bin; virtualhost.sh " & input & "; }"
do shell script vhost user name "user" password "pass" ¬
with administrator privileges
If you want a relative path, you can put virtualhost.sh inside the script application or bundle (e.g. in Contents/Resources), either in Terminal or by control-clicking and choosing "Show Package Contents". Then use "path to me":
set vhostPath to "'" & POSIX path of (path to me) & ¬
"/Contents/Resources/virtualhost.sh" & "'"
set vhost to vhostPath & space & input
do shell script vhost user name "user" password "pass" ¬
with administrator privileges
Per the comment on my other answer, I'm posting a secondary answer more in the spirit of that there's a will, there's a way, but it's a different, more dangerous approach. However, it's the only solution I can think of to this particular requirement (to get the interactivity of Terminal, but without having to prompt for an administrator password while running a script as administrator).
This solution runs Terminal as root, which is what do shell script "command" with administrator privileges does. This is dangerous because you have an open Terminal window with root access, so carefully weigh benefits against potential consequences of, say, opening a new Terminal window and being at the Bash prompt as root.
For this reason, the Terminal instance that is opened is killed upon completion of the script; if the "kill" command is removed, be aware you'll get multiple instances of Terminal, rather than multiple windows within the same instance.
No idea if this works in AppleScript Studio (it works in AppleScript Editor), but I can't think of any reason why it wouldn't.
set input to "some_input"
set vhost to "/usr/local/bin/virtualhost.sh " & input
set kill to ¬
"terminal_pid=$(</tmp/terminal_pid); rm /tmp/terminal_pid; kill $terminal_pid"
-- launch Terminal as root, and save its process ID in /tmp/terminal_pid
tell application "Finder" to set beforeProcesses to processes
do shell script ¬
"/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal " & ¬
"&> /dev/null & echo $! > /tmp/terminal_pid" user name "user" password ¬
"pass" with administrator privileges
-- wait until the new Terminal is confirmed to be running
tell application "Finder"
repeat while (processes is equal to beforeProcesses)
do shell script "sleep 0.5"
end repeat
end tell
-- Perform script in root Terminal window that we just opened,
-- and kill Terminal when done to prevent open root prompt
-- and multiple processes.
tell application "Terminal"
activate
do script vhost & "; " & kill
end tell
-- optional: wait until Terminal is gone before continuing
do shell script "while [[ ( -f /tmp/terminal_pid ) " & ¬
"&& ( \"$(ps -p $(</tmp/terminal_pid) -o%cpu='')\" ) ]]; do sleep 0.5; done"

Resources