Automator for Administrators
Volume Number: 22 (2006)
Issue Number: 9
Column Tag: Automator
Automator for Administrators
Using your scripting prowess in a new way!
by Philip Rinehart
Automator
With the release of Tiger, a new utility was added to the normal suite of Apple applications, Automator. A grizzled system administrator at this point might ask, so what? What can it do to make my life easier? The answer lies in the original project name before Tiger was released, pipeline. As system administrators know, a pipe is a very quick way to chain events together from the command line. Beginning to see the connection?
One line from the Apple Help documentation sums it up best, "Automating repetitive tasks can save a lot of time and effort". While most Automator tasks that are shipped with Macintosh OS X are Application specific, they don't do much for the system administrator. Let's begin the journey by examining an action in the System Automator actions.
RUN Shell Script
The first action should seem familiar, as it is similar to the do shell script command that has existed in Applescript for some time. With the 10.4.2 update to Macintosh OS X, the Run Shell Script action was added to the suite of Automator actions. The Run Shell Script does exactly as its name implies. It runs a shell script. The default action when added to an Automator workflow is shown in Figure 1.
Figure 1
When a system administrator writes shell scripts, a she-bang #! is used as the first line of the script to tell the system what to do with the text. Automator automatically interprets the script for you, defaulting to the bash shell. The action even goes one step further, including some common shells, csh, tcsh, sh, zsh, ksh, and two programming languages, python and perl. Sorry, ruby fans! By default, the action takes input as if it were being sent directly from standard input. The last option, Show Action When Run opens a dialog, allowing the user to change the options as entered in the action itself. However, before examining this particular option, let's create our first workflow.
First, the action needs some input to work with. How would a system administrator start a pipe from the command line? ls springs to mind, as often as a system administrator is working on a set of files. This action can be mimicked in Automator by using the Get Specified Finder Items action. Figure 2 shows an example:
Figure 2
Any result from this action is passed to the Do Shell Script automator action, with the path listed above, and you perform the entered shell script on the item.
Next, drag the Do Shell Script action as shown in Figure 1 beneath the Get Specified Finder Items action. Instead of using the default Pass Input method, change the method to as arguments. It will become clear why this change is preferred in a moment. As the action uses bash as its default shell, enter ls -l $@. This will return a long listing of the folder /Users/Shared. Try it out!
But .... the action runs, the submarine sound plays, and two green check boxes appear. However, there aren't any results! How does one get results? Simple, add the View Results to your action as the third item. Once added, the results will be passed as text, allowing your action to be checked for accuracy. This action is typically added when developing an action, but not used in the final production workflow.
The Do Shell Script action is useful, but there are some important limitations. If this action is saved as an automator application, it will simply run but present no tangible result. Not very useful. The action could be saved as a workflow, but that allows any user to alter the contents of the action. At best, the action will simply fail or produce unpredictable results. At worst, it has the potential to destroy the contents of a user's hard drive. rm -rf anyone? What a system administrator really wants is the ability to create an action for the environment that they administer, with any customizations needed. Enter Xcode.
THE meat of it
Xcode 2.1 and Shell Script Automator Action
Xcode 2.1, introduced at WWDC 2005, added the ability to create a custom shell script action. The mind reels at the possibilities! Since then, Xcode has been updated to 2.2.1, fixing some bugs along the way. The custom shell script action is created as any Xcode project is, simply by selecting New Project from the File menu, and then selecting the Shell Script Automator Action. The action will be created with an Xcode template as shown in Figure 3.
Figure 3
Xcode will create a number of files by default:
as well as linking the required frameworks to build the action.
Parts is parts
While Xcode may be familiar to programmers, or those who have dabbled in writing Cocoa or Carbon programs, many system administrators may not be as familiar with the interface. The first file we will examine is main.command. Double click on the file to open it. Figure 4 shows the window that appears:
Figure 4
Look familiar? This file contains the shell code used in the Automator action. Note that the she-bang is included, and by default uses sh (an alias to bash in Tiger). However, you don't have to use shell, you can use any available language on your system, ruby, tcl, whatever suits your environment.
As with any shell script, it can be considered best practice to add the PATH environment variable. Additionally, I usually add the following for security and debugging:
set -xv
exec 2>/dev/console
echo starting action... 1>&2
printenv 1>&2
IFS='
'
Although we are talking about Automator, this shell prologue could be used for any shell script that is being written in the bash shell. The first line, set -xv turns on verbose mode, and also expands each command. Next, all output is sent from stderr to the console, which is useful in debugging automator actions. The following two lines simply put some information content into the console. The last item protects the Input File Separator (IFS) from improperly formed input, a potential security risk for any shell script.
Figure 5 shows an automator action using the prologue and setting the PATH variable.
Figure 5
Take it for a whirl. Create an automator action, enter the prologue code and build and run. What happens? Open the Console, the environment should appear as shown by printenv, as well as a "starting action..." message.
BUILDING the ui
When you build a project, a default user interface is built for the project. Double click on main.nib. Figure 6 shows main.nib, the default UI.
Figure 6
Pretty bare, and not very useful, isn't it?
First, add the Cocoa-Automator Palette to XCode. The process is not too complicated, but does require knowing all the right steps. Here's how to add it:
1. Choose the Palettes Menu
2. Select Palette Preferences. A window will open.
3. Click the Add... button
4. Select the AMPalette.palette in /Developer/Extras/Palettes
A new palette will appear with the Automator icon. This palette can be used to add three types of actions to the custom action. The UI also accepts any other object from the Interface Builder palette. All the standard Interface Builder rules still apply, including Human Interface Guidelines, backgrounds, et al. When finished, be sure to save the file, usually main.nib, as it is the interface of the custom action.
Connecting the UI
Once you have created the UI, how does the main.command file know about the controls you have created? Bindings! Not a Cocoa programmer? No matter, bindings in the context of creating a Custom Automator Action are pretty simple to use. Back to the UI. Open main.nib in Interface Builder. If the Inspector is not open, open it from the Tools menu. To make this process clearer, here's a step-by-step walkthrough.
Step 1: From the Automator palette, select the Directory Chooser button. Drag it onto the UI and position it. The Inspector pane should be similar to Figure 7.
Step 2: Select the path triangle, and expand it. This parameter binds a value for use in the main.command file
Step 3: Enter a name in the Model Key Path field. It can be any value that makes sense, for this example, enter directoryPath. The value is critical as it supplies a value to the custom shell script. Bindings become environment variables in the shell script. If you think of setting the binding values in this way, it should begin to make more sense.
Step 4: Now that the binding is done, save the UI. Open main.command and add one line, echo $directoryPath. The newly created binding is echoed out as a result.
Step 5: Build and run the custom action. The custom action will appear in the Terminal section of the Automator Interface. Add the action as the first step in the workflow, with the next action of View Results. Choose a new Directory Path. It should appear in the View Results pane. If not, double check that the binding box is checked in Interface Builder, and that the environment variable is set correctly. One important note, accepting the default value presented in the UI will return a null value. Change the value at least once, and the new value will be returned.
Figure 7
The three objects on the Automator palette, all use bindings in the same way as the previous example. Enter a Model Key Path, and then use the value in the shell script. Other objects are a little trickier. Switches return a Boolean value of 0 or 1 based on the selection state, and use the Value binding. Radio buttons return the Index value starting at 0, using the Selected Index binding.
All the pieces needed to make a custom Automator action are now in place. Run the action once more from XCode. Select the action, and read the box in the left bottom corner. Not exactly what one would deliver to an end-user. There is no indication of what the action does, warnings, or anything else! Select any other action, and it should be clear what is needed.
Making it useful
Custom Automator Shell Script Actions are by definition also XCode projects. Because of this fact, each template has an auto-generated Info.plist as well as a localized Infoplist.strings file. The information from the Infoplist.strings file appears in the bottom left window for the custom action. Additionally, there are other keys in the Info.plist file that can be modified to provide a category, icon, descriptions, or other items that will make it clear what the custom action does.
The most logical place to begin is with the items that are most visible when using the custom action. The following keys appear in Infoplist.strings:
AMDAlert = "(* AMDAlert text goes here. (optional) *)";
AMDInput = "(* AMDInput text to further explain the types accepted as input goes here. (optional) *)";
AMDNote = "(* AMDNote text goes here. (optional) *)";
AMDOptions = "(* AMDOptions text to further explain configuration options in the UI goes here.
(optional) *)";
AMDResult = "(* AMDResult text to further explain the types provided as output goes here.
(optional) *)";
AMDRequires = "(* AMDRequires text to explain anything outside of Automator required for the
action's operation, e.g. a web page open in Safari, goes here. (optional) *)";
AMDSummary = "(* AMDSummary text to explain what your action does goes here. *)";
Each one of the keys is fairly clear about its purpose. Note, each key has some default text provided by Xcode, as well as whether it is optional. The only key that is not optional is the summary of the action. One other caveat, each one of these keys is included in the default Info.plist. If all the keys are removed from the Infoplist.strings, the Info.plist values are used instead. Best practice dictates that the strings file is used, so that different localizations can be provided for the custom action.
Next, give the custom action a name. The name appears in the Applications section of Automator. Edit the Info.plist value for AMApplication. This value will then appear in Automator when you build and run the XCode project. Note, Automator caches values, and may require the use of the Clean All option. This option removes all caches, and will display the newly entered value. Try it! If all goes as planned, the custom name will appear in the Applications folder in Automator.
It now has a descriptive name for the Applications tree, but the actual custom action will still be named the name of your XCode project. If you wish to keep the name of your XCode project as the custom action name, all done! If not, open Infoplist.strings and change the value for AMName. Again, follow the Clean All, Build, and Run routine. Now, when the custom category is selected, the workflow should reflect the custom name.
Now that the custom action reflects a consistent name and category, I want to have my cake and eat it too with a custom icon! Custom icons for automator actions are no different than creating any custom icon for an XCode project. Fortunately, it is slightly simpler to create icons for a Custom Action. Create a 32 x 32 tif in any graphic editing application. Using the Add to Project menu from XCode, add the tif file. Copy it into the project. Next create another tiff, but name it customiconLarge.tif. This icon will then be represented in the lower left pane. Add it to the project in the same way that the previous tif file was. When the project is built this time, the custom action should have the new custom icon.
Wow, that is a lot of customization! There is even more!
Advanced Preferences
AMKeywords
This preference associates keywords with a custom action. It gives another level of searchability within Automator. As an example:
<key>AMKeywords</key>
<array>
<string>Finder</string>
<string>Spotlight</string>
<string>Security</string>
</array>
This array adds keywords so that when an Automator user uses the built-in search functionality, the custom action is displayed in the results.
AMRequiredResources
This plist dictionary is, perhaps, one of the strongest ways to make sure the custom action functions as expected. The action can tie to an application, or a file as below.
<key>AMRequiredResources</key>
<array>
<dict>
<key>Display Name</key>
<string></string>
<key>Resource</key>
<string>/opt/local/bin/perl</string>
<key>Type</key>
<string>file</string>
<key>Version</key>
<string>5.8.8</string>
</dict>
</array>
Note the use of a specific version of perl. If the file is not found, the custom action will not appear in Automator. This preference allows the action to be strongly bound to a specific version of a file. Potentially, this binding is critical, as if a function is used that is specific to a particular version of a programming language, it can be anticipated and planned for by the custom action author.
AMWarning
This preference causes Automator to display a dialog notifying the end-user about specific requirements of the action.
<key>AMWarning</key>
<dict>
<key>Action</key>
<string>com.apple.Automator.SpecifiedFiles</string>
<key>ApplyButton</key>
<string>Sure!</string>
<key>IgnoreButton</key>
<string>Not at this time</string>
<key>Level</key>
<integer>2</integer>
<key>Message</key>
<string>This action requires the Specified Files action, can I add it for you?</string>
</dict>
Action uses the bundle identifier of an action that will be inserted before the custom action if the user agrees. ApplyButton is the string that is displayed by the button that either continues the workflow or adds a proposed action. Think of this button as the "OK" button. IgnoreButton is the string that is displayed by the button, which either stops the workflow, or cancels adding the proposed additional action. Level is an integer that gives the level of the warning. 2 is used for irreversible changes, 1 is used for reversible changes, and 0 is for a safe action. The last key, Message, must be used if the level is anything other than 0.
In the AMWarning example, the Automator action Get Specified Files is added if the user agrees. Additionally, this warning is a level two warning, and will present the yield sign with an exclamation point. A level one warning uses the Automator icon. It is also important to note, that the AMWarning values need localization, and should also be entered in the Infoplist.strings file.
AMDefaultParameters
This last dictionary is also quite useful, as it allows the action creator to specify default values when the action is added. Simply add the binding name, and its default value.
<key>AMDefaultParameters</key>
<dict>
<key>world</key>
<integer>1</integer>
</dict>
This example uses the value of 1 assigned to the binding key world.
Phew! That's a lot of work to make a custom action. It will pay off though, as a very clear, straightforward action can be provided for users. A primary reason for developing a custom shell script action is to make life easier for the end-user, and by extension easier for a system administrator.
Back to the SCRIPT
And now, back to our regular program. A lot of time has been spent exploring the user interface, values that construct that interface, and the look and appearance of the interface. However, the real purpose of this article is to examine how to write a custom shell script action. Believe it or not, it is actually the easiest part of creating a custom action. Look at an example:
#!/usr/bin/env sh
# main.command
#Prologue
PATH=/bin:/usr/bin:/sbin:/usr/sbin:/opt/local/bin export PATH
set -xv
exec 2>/dev/console
echo starting action... 1>&2
printenv 1>&2
IFS='
'
array1=( $(cat) )
for x in "${array1[@]}"
do
if [ $world -eq 1 ]
then
test=`find -x "$x" -perm -0777`
if [ -n "$test" ]
then
echo "World Writable Files found in $x" >> /tmp/results
echo >> /tmp/results
echo "$test" >> /tmp/results
echo >> /tmp/results
else
echo "No World Writable files found in $x" >> /tmp/results
echo >> /tmp/results
fi
fi
done
exit 0
The prologue as presented before is included for security reasons. Next, the list of files or folders provided to the shell script is assigned to an array of values. Input to the action should be treated as a series of lines presented to the script. If the Get Specified Finder Items action is used, the value of array1 is a list of all the file paths.
Once the value is assigned to the array, an author will usually want to do something to each item in the array. The for loop above, reads the value of $world, a custom binding created in the user interface. In this case, the binding is for a checkbox, which has a Boolean value of 1 or 0. Its state is then tested, and written to a file in /tmp.
The last step provides an exit value. Custom actions must have an exit value, to properly terminate. While incomplete, this shell script should provide some food for thought about writing your own custom action.
Last thoughts
All the pieces are in place to write your own custom shell script action. Remember, the sky's the limit! Anything that can be done with a shell script, can now be provided as an automator action, with a GUI. Don't like using the Archive tool provided by the Finder? Write your own using gnutar? or gzip, or bzip2. Get out there, and start writing custom actions now that you know how!
Philip Rinehart is co-chair of the steering committee leading the Mac OS X Enterprise Project (macenterprise.org), and is the Lead Mac Analyst at Yale University. He has been using Macintosh Computers since the days of the Macintosh SE, and Mac OS X since its Developer Preview Release. Before coming to Yale, he worked as a Unix system administrator for a dot-com company. He can be reached at: <philip.rinehart@yale.edu>.