Welcome to Brian's Bits, where Brian gets to share at length about various topics stirring inside of him.
AppleScripting the Mac User Interface
2 December 2014
Today’s article is the fourth, and last, in a series about how to use Siri on your iOS device, in partnership with AppleScript on your Mac, to remote voice control your Mac. In the midst of this series, I have also taken a little detour to explain how to set up a virtual aggregate audio output device on a Mac. You can find these articles by following these links: When figuring out the AppleScript commands to control Bluetooth and audio devices, I discovered a number of AppleScript-specific techniques which I found thrilling (being a professional programmer!), and which you may find useful (and thrilling!) as well. For details about how to implement my SiriListener AppleScript, see the previous articles. But for details about HOW I created the script, and the programming challenges I overcame, read on!

First of all, if you have not done so already, you need to download a copy of the latest version (2) of my AppleScript program: SiriListener2.scpt. I analyzed and explained a large portion of the script in my previous articles. Today we are going to go through much of the handlers (subroutines) section line by line. I have some valuable discoveries I would like to share with you! In my SiriListener script, I want to be able to pair my Mac with any of my Bluetooth audio devices. I also want to be able to switch my Mac’s audio output to any of my speakers or headphones.

Because there are no AppleScript commands to perform these tasks directly, it is necessary to manipulate the Mac’s user interface (UI) via AppleScript to perform the same tasks that I would perform if I were sitting in front of my Mac. Because this statement sounds pretty abstract, let’s take a look at a specific example.

In order to change the current audio output device (without using the Sound icon on the main system menu bar), I need to open the System Preferences app, click on the Sound icon, click on the Output tab button, and then click on the name of the device I want from the list of available devices.

The challenge is to perform these steps through AppleScript rather than through my hand clicking on the mouse as I sit in front of my Mac. In order to do so, the AppleScript code needs to find out which audio output devices are in that list, and then choose the correct one. That is the tricky part.

Unfortunately, the Script Editor app’s references and help didn’t provide the necessary information to enable me to figure out this tricky part on my own. Because I certainly couldn’t be the first person who wanted to perform these operations with AppleScript, it was time to turn to the Internet for some answers. Fortunately, as I was searching around the Web for what others have to say on this subject, I came across a gem of a script in a forum on the Macworld Web site. The article title — Finding Control and Menu Items for use in AppleScript User Interface Scripting — says it all!

Even better, I found an improved version of this script on another Web site as I was writing this article. Talented AppleScripters on the MacScripter.net site offered this solution which I have modified and enhanced further by combining additional solutions offered in that article, plus a few touches of my own:
property ScriptTitle : "Get UI Elements v2"
-- By squaline (alias partron22?), emendelson, Nigel Garvey, and Brian Byrd.
-- This version published at http://briansbits.com/applescript.php
-- Modified from scripts found at http://macscripter.net/viewtopic.php?pid=146903#p146903

on main()
  tell application "System Events" to set appNames to name of application processes whose visible is true
  tell application (path to frontmost application as text)
    set appChoice to (choose from list appNames with prompt "Which running application?" with title "Choose an application")
    if (appChoice is false) then error number -128
    set appName to item 1 of appChoice
    set setChoice to (choose from list {"Front Window", "Menu Bar"} with prompt "Which UI element set(s)?" with title "Choose an element set" with multiple selections allowed)
    if (setChoice is false) then error number -128
    set setFlag to ((setChoice contains "Front Window") as integer) + (((setChoice contains "Menu Bar") as integer) * 2)
  end tell
  tell application "System Events"
    tell application process appName
      set frontmost to true
      set {windowExists, menuExists} to {front window exists, menu bar 1 exists}
    end tell
  end tell
  set {winstuff, menustuff} to {"(No window open)", "(No menu!)"}
  if ((setFlag is not 2) and (windowExists)) then set winstuff to elementListing(appName, "front window")
  if ((setFlag > 1) and (menuExists)) then set menustuff to elementListing(appName, "menu bar 1")
  tell application "TextEdit"
    delay 2 -- give TextEdit time to open before creating a new document. Added by Brian Byrd 11/24/2014.
    make new document at the front
    set the text of the front document to item setFlag of {winstuff, menustuff, winstuff & linefeed & "-----" & linefeed & menustuff}
    set WrapToWindow to text 2 thru -1 of (localized string "&Wrap to Window")
  end tell
  delay 1 -- give TextEdit time to load the text before performing the Wrap to Window menu command. Added by Brian Byrd 11/24/2014.
  tell application "System Events"
    tell application process "TextEdit"
      key code 126 using {command down} -- command up-arrow keystroke to move the text cursor to the beginning of the document. Added by Brian Byrd 11/24/2014.
      tell menu item WrapToWindow of menu 1 of menu bar item 5 of menu bar 1
        if ((it exists) and (it is enabled)) then perform action "AXPress"
      end tell
    end tell
  end tell
  return -- nothing.
end main

on elementListing(appName, elementSet)
  return text 1 thru -2 of (do shell script ("osascript -e 'tell app "System Events" to tell application process "" & appName & "" to get entire contents of " & elementSet & "' -s s |
sed -E 's/"System Events", /"System Events"'$'n''/g ; # Put each ''list item'' on its own line.' |
sed -E '# Make two lines from the first: the first with the root reference and the second with the indented first child specifier.
  1 s/^{(.+) of ((window |menu bar [^i]).+)$/2'$'n'$'t''1/ ;
  1 !{ # Extract and indent the specifiers from the remaining lines:
  s/ of ((window |menu bar [^i]).+$)?/'$'t''/g ; # Substitute tabs for the root reference and all '' of ''s.
    s/("[^'$'t''"]+)'$'t''([^"]*)/1 of 2/g ; # Reinstate '' of ''s between quotes.
    t tillDone
  s/'$'t''[^'$'t'']+/'$'t''/g ; # Zap the stuff after each tab, leaving the current specifier & trailing tabs.
  s/^([^'$'t'']+)(['$'t'']+)$/21/ ; # Swap round the specifier and the tabs.
  } ;'") without altering line endings)
end elementListing

In order to interact with user interface objects via AppleScript, there are two pieces of information we need:
  1. The name of the object.
  2. The hierarchy of the object in the window — often one object is contained within another object.
The above script will give us this information for every object in the target window. It will also give the complete menu structure for the target app. Although I have not had much need for the menu structure information while writing SiriListener, it could be very useful for other programming projects.

It will be helpful to look at a concrete example. In my SiriListener script, I need to find out the names of all the audio output devices. So here’s what I need to do:
  1. Open the Script Editor app from the Applications | Utilities folder.
  2. Copy the above script and paste it into a new, empty Script Editor document window.
  3. Open the System Preferences app.
  4. Navigate to the Sound pane, and then click on the Output tab button. You should see something very similar to the screen shot above (or below).
  5. Back in Script Editor, click on the Run (Play) button, or press Command (⌘) R.
  6. When the “Choose an application” dialog box appears, choose System Preferences and then click the OK button.
  7. When the “Choose an element set” dialog box appears, choose Front Window and then click the OK button.
After a few seconds, the following object hierarchy listing will appear in a new TextEdit window:
window "Sound" of application process "System Preferences" of application "System Events"
  image 1
  image 2
  checkbox 1
  static text "Output volume:"
  slider "Output volume:"
    value indicator 1
  tab group 1
    radio button "Sound Effects"
    radio button "Output"
    radio button "Input"
    scroll area 1
      table 1
        row 1
          text field 1
          text field 2
        row 2
          text field 1
          text field 2
        row 3
          text field 1
          text field 2
        row 4
          text field 1
          text field 2
        row 5
          text field 1
          text field 2
        row 6
          UI element 1
          UI element 2
        row 7
          text field 1
          text field 2
        column 1
        column 2
        group 1
          button "Name"
          button "Type"
      scroll bar 1
        value indicator 1
        button 1
        button 2
        button 3
        button 4
    static text "Select a device for sound output:"
    static text "Settings for the selected device:"
    static text "The selected device has no output controls"
    button 1
  checkbox "Show volume in menu bar"
  button 1
  button 2
  button 3
  toolbar 1
    group 1
      group 1
        button 1
        button 2
    group 2
      group 1
        button "Show All"
    group 3
      static text "Sound"
    group 4
      text field 1
        button 1
        button 2
Armed with the above output, we can now figure out how to find the names of the audio devices listed in the Sound Output pane of System Preferences. Let’s take a look at a second screen shot of that window.

As you can see, the sound output devices are listed in some sort of scrolling table, which has two columns with the headings “Name” and “Type”.

Looking at the object hierarchy listed above, we can see an object called “scroll area 1”. Inside of that is “table 1” which has a certain number of rows, and each row has two text fields, which would correspond to the two columns.

The exception is row 6 of the table, which has two UI elements instead of two text fields. These UI elements correspond to the line you can see in one of the rows in the screen shot to the right. There is no text in the two columns of that row, only two graphical lines which join together to make a single solid line. We will have to take this different type of row into account in the script below.

For the purposes of the SiriListener script, I need to examine the contents of “text field 1” for each row of the table. When I have found the speaker name I am looking for, I can set that speaker as the active sound output device. Let’s take a look at the AppleScript handler (subroutine) I wrote to accomplish this task:
  1. on ActivateSpeakers(SpeakerName)
  2.   tell application "System Preferences"
  3.     activate
  4.     set the current pane to pane id "com.apple.preference.sound"
  5.     reveal anchor "Output" of pane id "com.apple.preference.sound"
  6.   end tell
  7.   try
  8.     tell application "System Events"
  9.       tell application process "System Preferences"
  10.         set AudioOutputs to table 1 of scroll area 1 of tab group 1 of window "Sound"
  11.         repeat with Counter from 1 to (number of rows of AudioOutputs as integer)
  12.           if exists text field 1 of row Counter of AudioOutputs then
  13.             set TheSpeaker to value of text field 1 of row Counter of AudioOutputs as string
  14.             if TheSpeaker is equal to SpeakerName then
  15.               set selected of row Counter of AudioOutputs to true
  16.               exit repeat
  17.             end if
  18.           end if
  19.         end repeat
  20.       end tell
  21.     end tell
  22.     my ClosePreferences() -- comment out this line if you don't want to close the System Preferences app
  23.   on error
  24.     my OpenSecurityPane()
  25.   end try
  26. end ActivateSpeakers -- end ActivateSpeakers handler
I’ve numbered the lines in this listing to make it easier to reference each statement. Line 1 is the handler (subroutine) definition. The handler has the name “ActivateSpeakers” and has a parameter variable named “SpeakerName”.

Line 2 through Line 6 is a block of code called a compound statement. I didn’t invent it myself, but found it in a script on some Web page. Line 2 is instructing the AppleScript to direct all of the statements until the end of the block (Line 6) to the System Preferences app.

Line 3 will cause the System Preferences app to be run if it is not already running. Line 4 opens the “Sound” pane in the System Preferences app, as if you were sitting in front of your computer and clicking on the “Sound” icon in the app. Line 5 opens the “Output” tab page in the “Sound” pane, again, as if you were sitting there doing it yourself.

Because statements later on in this script may cause an error, I am putting all of the rest of the code into a  try ... try end  block, from Line 7 through Line 25.

The operations the script needs to perform in order to get the list of audio devices cannot be performed directly on the System Preferences app. Instead, they need to be routed through the System Events app. This background app is a part of OS X, and it provides an interface for AppleScript to control various parts of the OS X system. Therefore, most of the remaining statements are found inside the  tell application "System Events" ... end tell  block, from Line 8 through Line 21.

The System Events app will be performing its operations on the System Preferences app, so the block of code from Line 9 through Line 20 is directed to System Preferences. Now that the stage has been set, we're ready to start actually doing some useful work in Line 10 through Line 19. As we saw in the Sound Output pane object hierarchy listing above, the list of audio output devices is contained in a table named “table 1” which is located inside “scroll area 1” which is located inside “tab group 1” which is located in window “Sound” of application process “System Preferences” of application “System Events”. Whew!

The location of the table within the UI could be written in a more compact path format:
application System Events / process System Preferences / window Sound / tab group 1 / scroll area 1 / table 1
Line 8 of the above script takes care of the application System Events part of the path. And Line 9 of the above script takes care of the process System Preferences part of the path. That leaves us with this reduced address for the table of audio output devices:
window Sound / tab group 1 / scroll area 1 / table 1
The only way to access this table is by using its address. Because we are going to refer the table numerous times, it would be very handy to use a shorhand notation that is not so long and unwieldy.

In English we use nicknames to shorten a name, like using Al instead of Alexander. In programming we can do something similar by assigning the address of the table to a variable.

Variables in computer programs are commonly used to hold the value of a number, like 123, or a piece of text, like “I love AppleScript!” But because AppleScript is an object-oriented language, we can just as easily assign an object  to a variable, and then use that variable to reference the object. In Line 10 I am doing just that by assigning the address of “table 1” to the variable AudioOutputs.
set AudioOutputs to table 1 of scroll area 1 of tab group 1 of window "Sound"
For the rest of the script, whenever I use the variable AudioOutputs, I am actually referencing the object called “table 1”. Let’s see how that works in actual use. In order to find a specific audio output device listed in the table, we need to examine the text in the first column of each row of the table. We accomplish this in a repeat ... end repeat loop — a block of code from Line 11 through Line 19.

Line 11 sets up the loop. Remember that the variable AudioOutputs represents the table we are examining. One of the AppleScript properties of a table is the number of rows it has. So we are going to look at each row, starting with the first row, all the way to the last row, which has the same row number as the total number of rows. In other words, if there are 7 rows, they are numbered row 1 through row 7.

Still on Line 11, the variable Counter contains the number of the current row we are looking at, starting with, as I just mentioned, the number 1. Each time through the repeat  loop the value of Counter will be incremented by one until we finish with the last row — or until we find what we are looking for!

In Line 12 we are checking to see if the current row we are examining in the table contains an object called “text field 1”. If you will remember what I mentioned above, a table row might contain UI elements (graphical lines) instead of text fields. If we try to reference a text field  that doesn’t exist in that row, then there will be an error and the script will fail unnecessarily. Therefore we perform this test to make sure a text field  exists in the current row.

If there is a text field  in the current row, Line 13 takes the text contained in the left column — “text field 1” — and assigns it to the variable TheSpeaker. In Line 14 we check to see if the name of the audio output device which we just put into the variable TheSpeaker is the same as the name of the audio output device contained in the parameter variable SpeakerName.

If the names match, then we have found the audio output device we are looking for! Therfore in Line 15 we give an AppleScript command to select the current row of the table. This is equivalent to sitting in front of your Mac and clicking on that row in the table with your mouse. This causes the audio output device listed in that row to become your Mac’s active audio output device.

Because we have found the device we are looking for, there is no need to keep looking. So in Line 16 we force our way out of the repeat  loop before we finish examining the last row of the table. And because we are done, the script exits the two tell  blocks of code as well.

The last statement this handler performs before it finished is Line 22. This statement executes another handler I wrote called “ClosePreferences”. That short handler simply tells the System Preferences app to quit. If you want the System Preferences window to stay open after this handler finishes, simply disable this statement by commenting it out with two hyphens at the beginning of the line.
Line 23 begins the block of code that gets executed when there is an error executing the handler. The primary and pretty much only error I am expecting occurs when the SiriListener.app script does not have permission to “control the computer”. In that case, Line 24 runs the OpenSecurityPane handler I wrote.

I have already explained all of the details of my error handling in two sections of two previous articles — see OS X: An Over-Protective Mother Hen and A Better Way To Handle Shuffle. I had also written a pair of handlers to connect to and disconnect from Bluetooth devices. Because these two handlers differed by only one statement, I decided to merge them into one handler which gets called two different ways, as shown below:
on ConnectBluetoothDevice(DeviceName)
  my ConnectDisconnectBluetoothDevice(DeviceName, "c")
end ConnectBluetoothDevice -- end ConnectBluetoothDevice handler

on DisconnectBluetoothDevice(DeviceName)
  my ConnectDisconnectBluetoothDevice(DeviceName, "d")
end DisconnectBluetoothDevice -- end DisconnectBluetoothDevice handler
Well, that’s kind of boring, so let’s take a look at the handler that does the real work:
  1. on ConnectDisconnectBluetoothDevice(DeviceName, KeyPressed)
  2.   tell application "System Preferences"
  3.     activate
  4.     set the current pane to pane id "com.apple.preferences.Bluetooth"
  5.   end tell
  6.   set ExitRepeat to false
  7.   try
  8.     tell application "System Events"
  9.       tell process "System Preferences"
  10.         set BTDevices to table 1 of scroll area 1 of window "Bluetooth"
  11.         repeat with Counter from 2 to (number of rows of BTDevices) -- 1st row is a header, so start with 2nd row
  12.           set DeviceNameObject to static text of UI element 1 of row Counter of BTDevices
  13.           -- each device name has two static text entries: the first line is the name of the device,
  14.           -- the second line is either "Connected" or "Not Connected"
  15.           -- in order to get the "name" of the device, we have to process each of these
  16.           -- static text entries separately, hence the following repeat
  17.           repeat with StaticTextEntry in DeviceNameObject
  18.             set TheDevice to name of StaticTextEntry
  19.             if TheDevice is equal to DeviceName then
  20.               select row Counter of BTDevices
  21.               tell BTDevices to perform action "AXShowMenu" -- opens the context menu as if you had right clicked
  22.               -- pressing "c" chooses "Connect" in the context menu
  23.               -- pressing "d" chooses "Disconnect" in the context menu
  24.               keystroke KeyPressed & return
  25.               set ExitRepeat to true
  26.               exit repeat
  27.             end if
  28.           end repeat
  29.           if ExitRepeat then
  30.             exit repeat
  31.           end if
  32.         end repeat
  33.       end tell
  34.     end tell
  35.     my ClosePreferences() -- comment out this line if you don't want to close the System Preferences app
  36.   on error
  37.     my OpenSecurityPane()
  38.   end try
  39. end ConnectDisconnectBluetoothDevice -- end ConnectDisconnectBluetoothDevice handler
If you compare the Bluetooth preference pane (right) to the Sound Output preference pane (above), you will find that they are fairly similar: there is a UI table which contains a list of devices. But the way the two tables arrange the information in each row differs significantly.

In the Sound Output pane, the rows listing the audio output devices are contained in “table 1” which is contained in “scroll area 1”. Each row contains two objects — “text field 1” and “text field 2” — with the name of each device displayed in “text field 1”.

In the Bluetooth preference pane, the rows are still located in “table 1” which is still located in “scroll area 1”, but the rows themselves have this structure:
row 1
  UI element 1
    busy indicator 1
    static text "Devices"
row 2
  UI element 1
    image 1
    static text "Logitech BT Adapter"
    static text "Connected"
row 3
  UI element 1
    image 1
    static text "AF62"
    static text "Not Connected"
row 4
  UI element 1
    image 1
    static text "Avantree sacool"
    static text "Not Connected"
row 5
  UI element 1
    image 1
    static text "GOBT2"
    static text "Not Connected"
row 6
  UI element 1
    image 1
    static text "Monster Clarity"
    static text "Not Connected"
Instead of each row containing two text fields, in the Bluetooth table each row contains only one object: a UI element — probably just a generic container for grouping other objects. In every row but one, the UI element contains three objects: two static text objects and one image object. The first row, which appears to be the header row, has a slightly different structure which does not concern us.

In the ActivateSpeakers handler listed above, I was able to get the name of the audio output device in each row by referencing  value of text field 1 — see Line 13 of that handler. But in the rows of the Bluetooth devices table, things are not so simple. It’s harder to get to the device name, but with a bit of trial and error I figured out a way to do it.

You will notice that the two static text objects in each row do not have different names, like static text 1 and static text 2. Instead, they are both simply called static text. This suggests that on each row there is a single static text object which contains two components. The tricky part is figuring out how to access those individual components. As the handler processes each row of the table, the first thing the script does is assign the static text  object to a variable named DeviceNameObject, as seen in Line 12. Here are the comments I put in the script at this point:
Each device name has two static text  entries: the first line is the name of the device, the second line is either “Connected” or “Not Connected”. In order to get the “name” of the device, we have to process each of these static text  entries separately, hence the following repeat  loop block.
The repeat loop on Line 17 is different than other repeat loops. It’s not like the repeat on Line 11 that loops through the statements in the block a fixed number of times. Nor is it like repeat until or repeat while which loop until a certain condition is met.

The repeat on Line 17repeat with StaticTextEntry in DeviceNameObject — is AppleScript’s version of the very useful foreach loop. Apple calls it a repeat with loopVariable (in list) statement.

This kind of loop is great when you have a collection of items, but you don’t know how many items there are. In our case, we know how many components are in the static text  object — two — but we don’t know how to reference those components directly. This foreach  type of repeat  loop let us access the components indirectly.

Remember that the statement in Line 12 assigns the static text object in the current table row to a variable named DeviceNameObject. Now, each time through this  repeat with StaticTextEntry in DeviceNameObject  loop, one of the static text components of DeviceNameObject will be assigned to the loop variable StaticTextEntry. At this point you might think that we have the information we need, but our difficulties are not over yet! Unfortunately, the static text component referenced by StaticTextEntry is not a text string, but some sort of object. I still had to figure out how to reference the text in that object. It was at this point that my more than 30 years of programming experience came to the rescue!

It would have been easy if I had the full user interface programming reference for the Bluetooth preference pane. But I’m not even sure such a reference exists outside of One Infinite Loop. Therefore I had to try my luck and employ some good guesses.

Using my past experience in programming with objects containing text, I started to try different names for the text string property of the static text component contained in the static text object referenced by the repeat  loop variable StaticTextEntry. Using AppleScript’s logging capability to view the results, I tried statements like  value of StaticTextEntry  and  text of StaticTextEntry  — but neither one worked.

Digging a bit deeper into my past programming experience, I tried another variation:  name of StaticTextEntry  — success at last! In Line 18 I assigned the retrieved text string to the variable TheDevice. In Line 19 I check to see if the name of the Bluetooth device which we just found is the same as the name of the Bluetooth device we are looking for.

If the names match, in Line 20 I select the current row in the table, as if I were sitting in front of my Mac and clicking on the row with the mouse. Line 21 tells the table of Bluetooth devices to perform a right mouse click (or ⌘ left click) on the selected row, which opens a context menu.

Line 24 is the only statement in this handler which is different, depending on whether you are connecting to a Bluetooth device, or disconnecting. If the target Bluetooth device is currently disconnected, the first entry on the context menu is the command “Connect”. Therefore, a simulated keystroke of the letter “c” will execute that menu command. If the target Bluetooth device is currently connected, the first entry on the context menu is the command “Disconnect”. Therefore, a simulated keystroke of the letter “d” will execute that menu command.

Line 24 performs that simulated keystroke, using the value contained in the handler parameter variable KeyPressed, along with a simulated keystroke of the Return (or Enter) key. Earlier I had shown you the two different calls to this handler:  my ConnectDisconnectBluetoothDevice(DeviceName, "c")  for connecting to a particular Bluetooth device, and  my ConnectDisconnectBluetoothDevice(DeviceName, "d")  for disconnecting from a particular Bluetooth device. Now that this handler has accomplished its mission, it is time to wrap things up as quickly as possible. Because the execution point of the code is currently within two nested repeat  loops, we need to break out of each one before they have had a chance to finish naturally. Line 25 set the ExitRepeat variable to true (in was initialized as false on Line 6) for use a few lines down.

Line 26 exits us from the inner repeat  loop. Line 29 checks to see if the ExitRepeat variable is set to true, and if so, Line 30 exits us from the outer repeat  loop. A couple of  end tell  statements and an  end try  statement later, we have reached the end of the ConnectDisconnectBluetoothDevice handler. The error handling in Line 36 and Line 37 functions just the same as the error handling in the ActivateSpeakers handler, which we have already discussed above.

Well, I suppose that is more than enough details for one article! I hope that what I have shared both helps you with any difficulties you are having in your own AppleScript coding, and also inspires you to take your skills farther by trying new techniques. There is so much that you can accomplish with AppleScript that the possibilities are virtually endless!

During your programming adventures, hopefully you won’t have as hard a time as our little programming friend in the following video. Be sure to leave your comments and questions about what I have shared in this article in the Feedback section below.
This article is 15th a series of articles on this Web site related to Technology and Computing which also includes (scroll to see the entire list):
26  Oct  2010
11  Jan  2014
29  Jan  2014
5  Feb  2014
7  Feb  2014
14  Feb  2014
15  Feb  2014
16  Feb  2014
17  Feb  2014
1  Nov  2014
12  Nov  2014
20  Nov  2014
22  Nov  2014
AppleScripting the Mac User Interface
2  Dec  2014
6  Dec  2014
Reader Comments
On April 27, 2016, WorkflowsGuy wrote:
Unfortunately, I cannot get the "Get UI Elements" script to run in AppleScript Editor 2.6.1

It will show

"Untitled.scpt:955:956: script error: Expected end of line, etc. but found “(”. (-2741)"

in line 16.
On May 21, 2016, rommel wrote:
I have the same thing happening as WorkflowsGuy in Applescript 2.8.1.
The script that is found in http://macscripter.net/viewtopic.php?pid=146903#p146903 will work however.
Thanks Brian for sorting this all out!
Article Index     |     Search     |     Site Help