Saturday, September 30, 2006

Running shell commands with Automator or AppleScript and executing with sudo privileges

Automator is very cool because it will give you a compiled app that can run AppleScript and execute the more powerful Unix shell commands as well.  Actually AppleScript gives you this ability too via the do shell script command i.e:
 do shell script "mkdir /Library/StartupItems" 


What this example shows you:
  1. How to show a dialog with AppleScript where you can ask for a hidden password that is not visible (not shown) as typed.
  2. How to run commands in a bash shell with sudo access in Automator.
  3. How to run bash shell commands in AppleScript with sudo access.

Ok, so now if you are like me, you know the power of Unix shell scripting, you also know it won't be long before you'll need sudo to run one or more commands in your script.  Now I did a quick search on the internet to just find some sample code, and in all my searching I found most people have not posted a good solution.  Most people in their sample scripts or Automator workflows resorted to storing the password in clear text within the script.  Not only is that a security risk, your script is no longer portable and has to be edited for every user.  I found responses like this basically saying it couldn't be done.

Ok, so here's what you do.  I created an Automator script, that first runs the following AppleScript (this would also work, just in plain AppleScript without Automator):

on run {input, parameters} 
    tell application "System Events"
        set the_username to do shell script "whoami"
        set the_password to "password"
        display dialog "You now need to enter the password for the currently logged in account: " & the_username & "

This account must have Administrator access to this computer." default answer "password" buttons {"OK", "Cancel"} default button "OK" with icon 2 with title "Password" with hidden answer
        set the_password to text returned of the result
    end tell
    return {the_password}
end run

Ok in the Automator script, then my next step in Run Shell Script (in bash of course).  That script looks like:

exec echo $1 | sudo -v -S; 
if [ $? -eq 1 ] then
    echo "you are not a sudoer"; else
    echo "you are a sudoer";  # replace this with your entire script.
    exec echo $1 | sudo -S ls /private/var/root/;
fi

Now all you have to do is put your sudo needing commands inside the else fi block and you're set.  If you want to download an Automator script with this code as an example, click here.
BTW if you are running within an AppleScript app you can call sudo like this:
do shell script "sudo ls /private/var/root/" password the_password with administrator privileges  

16 comments:

cambro543 said...

Very helpful, but there are some problems:

First, you forgot the ';' after if [ $? -eq 1 ]
The script will obviously not exectue in Automator without it and some of your readers (i.e., me) will copy and paste and wonder wtf.

Second, the link given to download an example Automator script (click here) is not a link.

Finally, I can't get it to work once I add the ';' and substitute:

echo "you are a sudoer";

with my script (chmod 666 /path/to/file).

Additional info on this post would be very useful!

Aric said...

cambro543 wrote:
Additional info on this post would be very useful!


No problem.

First, you forgot the ';' after if [ $? -eq 1 ]

Huh? Works fine. Maybe I'm missing what you mean. Where do you want the ';'?

Second, the link given to download an example Automator script (click here) is not a link.

You're right. The link was broken and is now fixed, so you can download an Automator script with all the code that works great!

Finally, I can't get it to work once I add the ';' and substitute:

echo "you are a sudoer";

with my script (chmod 666 /path/to/file).


Again, not sure where you are putting the ';'

Download the script now that the link is fixed and check it out. If you have more questions, or can explain the ';' question better, post another comment and I'll follow up.

axman6 said...

Wow, thanks heaps for this. i've now got a nice little app that asks for a password, checks to see if the processor performance is reduced or not, then reduces or not, and makes the display sleep either 1 or 60 mins.

I'll link you when i have it up somewhere :)

Al.

Jim said...

Thanks for this, now my sister can run a sudo shell script.... You solved a rather messy problem for me.

Sacrilicious said...

Thanks a lot for this, I was looking for a way to turn airport off from the keyboard. I've modified and updated it slightly as well:

on run {input, parameters}
tell application "System Events" to activate
tell application "System Events"
set the_password to "password"
display dialog "Admin password goes here." default answer "password" buttons {"OK", "Cancel"} default button "OK" with icon 2 with title "Password" with hidden answer
set the_password to text returned of the result
end tell
do shell script "sudo ifconfig en1 down" password the_password with administrator privileges
end run

running said...

Please, tell me you are joking. You really mean it, seriously, you just put your password as a PLAINTEXT into your applescript. Just talk about security!

Of course there is other way - just write
do shell script "rofllol" with administrator privileges

yeah, it asks for a password, but HELL, you don't just save your password like that! aaargh

Aric said...

Hey running, I'm assuming you can't read.

The entire point of this article was to show you how to do it so you don't have to save your password as plain text. But apparently, you can't read three sentences into the article.

QUOTE:
running said...
Please, tell me you are joking. You really mean it, seriously, you just put your password as a PLAINTEXT into your applescript. Just talk about security!

Of course there is other way - just write
do shell script "rofllol" with administrator privileges

yeah, it asks for a password, but HELL, you don't just save your password like that! aaargh

Kevin said...

This was a very useful post. Thank you!

Alex Angas said...

Brilliant - thank you!

John C. Welch said...

You're really overcomplicating parts of this. For example, you don't need System Events for "do shell script", that command is in Standard Additions.

If you look at do shell script, you don't need the dialog stuff either. Just:

do shell script "foo" administrator privileges true

that will prompt the user for a user ID and password, and uses the 'real' system auth dialogs to do so.

If you really want to get the user name of the currently logged in user, you don't need do shell script for that either. Again, from standard additions:

set theUser to short user name of (system info)

So using those lines:

set theUser to short user name of (system info)

do shell script "foo" administrator privileges true user name theUser

Aric said...

@John C. Welch. Well you got some of it right. Your tip about not needing System Events is correct so we can eliminate that. And I like getting the username from system info instead of running the shell script whoami. But I found the main command:
do shell script "foo" administrator privileges true user name theUser
Doesn't prompt me for my password and simply gives an error "Authorization failed." number -50

Megabyte said...

@running - This can be solved by chmod'ng the script file as only execute for the intended user(s), and chown'ng it a super user / group

Megabyte said...

@running - This can be solved by chmod'ng the script file as only execute for the intended user(s), and chown'ng it a super user / group

Vicente said...

John C. Welch had the closest answer. These single line work as intended, prompting me for my currently logged in user's password:

do shell script "foo" with administrator privileges

If you need tho know the username then you can, of course, also use his short way as:

set theUser to short user name of (system info)

Great discussion! Thanks.

Unknown said...

To start, I'm an audio engineer and not a programmer, so go easy on me.

When I'm using my audio application, spotlight runs down the system indexing every minute or so. I have a script that "unloads" the mds launch deamon and reload it when I'm done with my editing.

The scripts are these:

sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist

sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist

Because it's a sudo command, it requires a password and the reason for your post is to run these shell scripts using Automator to increase workflow.

I understand the purpose of your post, but not sure how to implement MY specific commands into your source code. Would you be so kind as to show me how to make that happen? I would greatly appreciate that!

I like your idea of making the script universal instead of isolated to one user. (I'm trying to make it easy for other engineer friends of mine to just click an automator app in the dock to turn spotlight off and on without having to deal with all the rigmarole.)

Unknown said...
This comment has been removed by the author.