Microsoft Excel

Ron de Bruin
Excel Automation

Microsoft MVP Program

AppleScriptTask in Mac Office 2016 or higher

In Mac Office there are many problems with the built-in VBA commands. VBA developers responded by using AppleScript in many situations to work around the problems. They also used AppleScript to do things that are not possible with VBA code; for example to email using VBA code.

In Office 2016 and higher, we need to use a new method and a new approach explained below.

 

Example for Excel 2011

In Office 2011 we use the built-in MacScript function to run a script that we build up as a string in the VBA code. See the code example below.

Copy the test macro and the function below into a normal module of your workbook. Change the file path and name in the macro TestMacro to point to a file on your Mac to test.

Sub TestMacro()
    Dim FileName As String
    FileName = "Macintosh HD:Users:RDB:Desktop:MacTestFile.xlsm"
    MsgBox FileExistsOnMac(FileName)
End Sub

Function FileExistsOnMac(Filestr As String) As Boolean
    Dim ScriptToCheckFile As String
    ScriptToCheckFile = "tell application " & Chr(34) & "System Events" & Chr(34) & _
    " to return (exists disk item " & Chr(34) & Filestr & Chr(34) & ") and class of disk item " & _
        Chr(34) & Filestr & Chr(34) & " = file "
    FileExistsOnMac = MacScript(ScriptToCheckFile)
End Function

When you run the macro named TestMacro it will test if the file :
Macintosh HD:Users:RDB:Desktop:MacTestFile.xlsm
exists on your Mac and display a msgbox showing True or False

Note: You can also use : FileName = "/Users/RDB/Desktop/MacTestFile.xlsm"

 

Excel 2016 or higher for the Mac

If you test the code that is working correctly in Excel 2011, in Excel 2016 and higher it gives a run-time error 5; but if you test the script string that the VBA code created in the Script Editor, the script works correctly.

What’s the problem here?

The legacy "MacScript" VB Command is severely limited by Apple’s sandbox requirements: it will not work correctly in most situations in Office 2016 and higher.

Instead, Microsoft added a new VB command "AppleScriptTask" that accesses and runs an AppleScript file located outside the sandboxed app. This new approach is not as convenient: with the MacScript function you could have the script in the file itself, while with the AppleScriptTask method you need to distribute an extra file containing the script, and it must be placed in the specified location on the user’s system to have permission to run. This requires some user interaction the first time.

1) Test the AppleScript in the Script Editor ?

First we open the Script Editor on your Mac.

  1. Click on the Spotlight icon in the top right corner of your Mac.
  2. Enter "Script Editor" to find and open this program.
  3. Click the New Document button, and we are ready to start.

Tip: Right click on the Script Editor icon in the dock and choose Options>Keep in Dock so it is easy the next time to open it when you need it.

The script you want the VBA code to run looks like this now in the script editor:

tell application "System Events" to return (exists disk item "Macintosh HD:Users:RDB:Desktop:MacTestFile.xlsm") and class of disk item "Macintosh HD:Users:RDB:Desktop:MacTestFile.xlsm" = file

  1. Paste that inside the editor
  2. Press the Run button
  3. Observe the Result area at the bottom
  4. You see the result (True or False)

 

When your script works correctly in the script editor you have proved that nothing is wrong with the script. Now we go to step 2 to make the script ready for using it with AppleScriptTask.

 

2) Add the script inside a handler and test it

Replace the script in the Script Editor with the script below.

on ExistsFile(filePath)
        --check if file exists and type is file
        tell application "System Events" to return (exists disk item filePath) and class of disk item filePath = file
end ExistsFile

And for testing only we copy this line at the top

ExistsFile("/Users/RDB/Desktop/MacTestFile.xlsm")

You can use this line instead if you want to use the colon separator

ExistsFile("Macintosh HD:Users:RDB:Desktop:MacTestFile.xlsm")

So it looks like this :

 

  1. Now save the file as MyFileTest.scpt on your Desktop.
  2. Use (File>Save)in the Script Editor Menu bar in the top left corner of your Mac window

You see that I placed the code inside a handler named ExistsFile which takes a parameter string of filepath. The name of the handler and parameter string is your choice. You see also in the script line that I replaced both path/file name strings with the variable filepath. This works the same as in the VBA code example for Excel 2011 where we have a string named FileName, which we created in the macro.

Before we try to run the script with AppleScriptTask in VBA we first test the handler inside the Script Editor. The first line in the script is there only for testing.

Press the Run button, and the script test if the file exists on your Mac. The line above the ExistsFile handler provides the filename string to the ExistsFile handler, to enable you to test the script before we take the next step of calling it from VBA.

Before we go to the next step remove the script line above the handler or make it a comment, so the script does not use it. You do this by adding two hyphens before the line so it looks like this:

--ExistsFile("/Users/RDB/Desktop/MacTestFile.xlsm")

Click on the Run button and you see that nothing happens, because the handler has no filename string to test. But it test and indents the changes in the script. It is important before you close a file after you make changes to press the Run button.

Close the script file now and you will notice that it has automatically saved your changes.

 

3) Where to place the script file for using it with AppleScriptTask

Now the script file is ready and tested we must copy it into the correct location. Follow the steps below to copy and paste it into this exact location.

  1. Open a Finder Window
  2. Hold the Alt key and click Go in the Finder menu bar
  3. Click Library
  4. Click Application Scripts (if it exists; if not create this folder)
  5. Click com.microsoft.Excel if it exists; if not create this folder (note: Capital letter E)
  6. Copy MyFileTest.scpt to the com.microsoft.Excel folder.

Note: If you want to use the example in Word you must add/use the com.microsoft.Word folder, each Office app have its own folder. Unfortunately there is no folder for all Office programs.

This are three ways to easily open the com.microsoft.Excel folder manual :

Note : Adding the folder to your Favorites is my favorite because you see the folder in your open and save dialogs in Excel.

Or use this script one time to create a folder for your Office files and create shortcuts to a few important folders, check out this page : Setup your Mac for Mac Office 2016 or higher

 

4) Use the script we create in VBA with AppleScriptTask

When you use AppleScriptTask the third argument is a parameter string that you use to give information to the handler. In the example on this page this must be the file path and name of the file that we want to test for.

This is the code line that you use in your VBA code: You see that there are three arguments:

  1. The script file name
  2. The name of the handler
  3. The file name you want to check

 RunMyScript = AppleScriptTask("MyFileTest.scpt", "ExistsFile", "/Users/RDB/Desktop/MacTestFile.xlsm")

So your VBA macro now looks like this :

Sub TestFile()
    Dim RunMyScript As Boolean
    Dim FilePathName As String
    
    FilePathName = "/Users/rondebruin/Desktop/MacTestFile.xlsm"
    
    RunMyScript = AppleScriptTask("MyFileTest.scpt", "ExistsFile", FilePathName)
    If RunMyScript = True Then
        MsgBox "File exists"
    Else
        MsgBox "File not exists"
    End If
End Sub

You can also add code to your workbook that check if the scpt file is in the correct location, copy the function below in the same module as your macro :

Function CheckAppleScriptTaskExcelScriptFile(ScriptFileName As String) As Boolean
    'Function to Check if the AppleScriptTask script file exists
    'Ron de Bruin : 6-March-2016
    Dim AppleScriptTaskFolder As String
    Dim TestStr As String

    AppleScriptTaskFolder = MacScript("return POSIX path of (path to desktop folder) as string")
    AppleScriptTaskFolder = Replace(AppleScriptTaskFolder, "/Desktop", "") & _
        "Library/Application Scripts/com.microsoft.Excel/"

    On Error Resume Next
    TestStr = Dir(AppleScriptTaskFolder & ScriptFileName, vbDirectory)
    On Error GoTo 0
    If TestStr = vbNullString Then
        CheckAppleScriptTaskExcelScriptFile = False
    Else
        CheckAppleScriptTaskExcelScriptFile = True
    End If
End Function

You can add this to your macro to stop it when the scpt file is not in the correct location

    'Check for AppleScriptTask script file
    If CheckAppleScriptTaskExcelScriptFile(ScriptFileName:="MyFileTest.scpt") = False Then
        MsgBox "Sorry the MyFileTest.scpt is not in the correct location"
        Exit Sub
    End If

 

More information

More than one handler in your script file

You can have more than one handler in the scriptfile; in the screenshot below I have also added a handler to test whether a nominated folder exists on your Mac.

Calling the folder test in VBA looks like this:

RunMyScript = AppleScriptTask("MyFileTest.scpt", "ExistsFolder", "/Users/RDB/Desktop/YourFolder/")

 

Use string wih more than one parameter in AppleScriptTask

AppleScriptTask can accept only one parameter string, but I found a workaround. See how I use SplitString in the applescript below to use more parameters.

 

More applescript examples that you can use with AppleScriptTask

Below you find some example scrips that you can copy in a scpt file and run it with VBA. I call the scpt file FileFolder.scpt in the macros below. So copy the scrips in a file FileFolder.scpt and copy it in the correct location on your Mac. You see that the check file and folder scripts from above are also in the scrips below.

--Ron de Bruin, www.rondebruin.nl
--15-aug-2020
--Applescripts for working with files and folders
 
 
on ExistsFile(filePath)
      tell application "System Events" to return (exists disk item filePath) and class of disk item filePath = file
end ExistsFile
 
 
on ExistsFolder(folderPath)
      tell application "System Events" to return (exists disk item folderPath) and class of disk item folderPath = folder
end ExistsFolder
 
 
on OpenFolder(folderPath)
      if ExistsFolder(folderPath) = true then
             do shell script "open " & quote & folderPath & quote
      else
             return "Error"
      end if
end OpenFolder
 
 
on CopyFile(paramstring)
      set {fieldvalue1, fieldValue2} to SplitString(paramstring, ";")
      if ExistsFile(fieldvalue1) = true and ExistsFolder(fieldValue2) = true then
             set sourceFile to fieldvalue1
             set sourceFile to quoted form of POSIX path of sourceFile
             set destinationFolder to quoted form of POSIX path of (fieldValue2)
             do shell script "cp " & sourceFile & space & destinationFolder
      else
             return "Error"
      end if
end CopyFile
 
 
on CopyFolder(paramstring)
      set {fieldvalue1, fieldValue2, fieldValue3} to SplitString(paramstring, ";")
      if ExistsFolder(fieldvalue1) = true and ExistsFolder(fieldValue2) = true and ExistsFolder(fieldValue2 & fieldValue3) = false then
             set Sourcefolder to quoted form of POSIX path of fieldvalue1
             set destinationFolder to quoted form of POSIX path of (fieldValue2 & fieldValue3)
             do shell script "cp -r -n " & Sourcefolder & space & destinationFolder
      else
             return "Error"
      end if
end CopyFolder
 
 
on CopyFolderMerge(paramstring)
      set {fieldvalue1, fieldValue2, fieldValue3} to SplitString(paramstring, ";")
      if ExistsFolder(fieldvalue1) = true and ExistsFolder(fieldValue2) = true then
             set Sourcefolder to quoted form of POSIX path of fieldvalue1
             set destinationFolder to quoted form of POSIX path of (fieldValue2 & fieldValue3)
             do shell script "ditto " & Sourcefolder & space & destinationFolder
      else
             return "Error"
      end if
end CopyFolderMerge
 
 
--Note : the script below is used by some of the scrips above
on SplitString(TheBigString, fieldSeparator)
      tell AppleScript
             set oldTID to text item delimiters
             set text item delimiters to fieldSeparator
             set theItems to text items of TheBigString
             set text item delimiters to oldTID
      end tell
      return theItems
end SplitString
 

And below you find the VBA macros to call the scrips above.

Sub OpenFolderInFinder()
'If you want to easy copy the path of the folder that you want to open in the script then
'Open the folder in Finder and be sure that in the Finder>View menu
'Show Path Bar is on, right click on the folder in the bar and choose copy ... as pathname
'Example below will open the Desktop in Finder, no need to change the code to test
Dim RunMyScript As String
Dim FolderPath As String

On Error Resume Next

FolderPath = MacScript("return POSIX path of (path to Desktop) as string")

'Also Desktop but you must enter the path yourself
'FolderPath = "/Users/rondebruin/Desktop"

'Example to open a folder named Ron that is on the Desktop
'FolderPath = MacScript("return POSIX path of (path to Desktop) as string") & "Ron"

RunMyScript = AppleScriptTask("FileFolder.scpt", "OpenFolder", FolderPath)
If RunMyScript = "Error" Then
    MsgBox "Wrong folder path"
End If
End Sub


Sub CopyFile()
'Copy file to another folder and replace if it already exists
Dim RunMyScript As String
Dim SourceFile As String
Dim DestinationFolder As String

'Add the path to the Source file and Destination folder
SourceFile = "/Users/rondebruin/Desktop/Source/Book1.xlsx"
DestinationFolder = "/Users/rondebruin/Desktop/Destination/"

'Add a slash at the end of the DestinationFolder path if the user forget it
    If Right(DestinationFolder, 1) <> Application.PathSeparator Then
        DestinationFolder = DestinationFolder & Application.PathSeparator
    End If
    
RunMyScript = AppleScriptTask("FileFolder.scpt", "Copyfile", SourceFile & ";" & DestinationFolder)

 If RunMyScript = "Error" Then
    MsgBox "Wrong file or folder path"
 Else
    MsgBox "Copy is ready"
 End If
End Sub


Sub CopyFolder()
'Copy folder to another folder
'If the destination folder not exits it will be created
'You can rename the Folder, see NameFolderInRootFolder
Dim RunMyScript As String
Dim SourceFolder As String
Dim DestinationRootFolder As String
Dim NameFolderInRootFolder As String

'Add the path to the Source and Root Destination folder
SourceFolder = "/Users/rondebruin/Desktop/Source/"
DestinationRootFolder = "/Users/rondebruin/Desktop/Destination/"

'Add the name of the folder in the root destination folder, this example add the Date/Time
NameFolderInRootFolder = "Source " & Format(Now, "dd-mmm-yy h-mm-ss")

'Add a slash at the end of the DestinationRootFolder path if the user forget it
    If Right(DestinationRootFolder, 1) <> Application.PathSeparator Then
        DestinationRootFolder = DestinationRootFolder & Application.PathSeparator
    End If

RunMyScript = AppleScriptTask("FileFolder.scpt", "Copyfolder", SourceFolder & ";" & DestinationRootFolder & ";" & NameFolderInRootFolder)

 If RunMyScript = "Error" Then
    MsgBox "Wrong path source or destination folder or destination folder already exists"
 Else
    MsgBox "Copy is ready"
 End If
  
End Sub


Sub CopyMergeFolder()
'Copy folder to another folder
'If the destination folder not exits it will be created
'if it exist it will merge the folder with changed and new files
'Note: It will not remove files
'You can rename the Folder, see NameFolderInRootFolder
Dim RunMyScript As String
Dim SourceFolder As String
Dim DestinationRootFolder As String
Dim NameFolderInRootFolder As String

'Add the path to the Source and Root Destination folder
SourceFolder = "/Users/rondebruin/Desktop/Source/"
DestinationRootFolder = "/Users/rondebruin/Desktop/Destination/"

'Add the name of the folder in the root destination folder
NameFolderInRootFolder = "Backup"

'Add a slash at the end of the DestinationRootFolder path if the user forget it
    If Right(DestinationRootFolder, 1) <> Application.PathSeparator Then
        DestinationRootFolder = DestinationRootFolder & Application.PathSeparator
    End If

RunMyScript = AppleScriptTask("FileFolder.scpt", "CopyfolderMerge", SourceFolder & ";" & DestinationRootFolder & ";" & NameFolderInRootFolder)

 If RunMyScript = "Error" Then
    MsgBox "Wrong path source or destination folder"
 Else
    MsgBox "Copy is ready"
 End If
  
End Sub

 

Create SCPT files with VBA code and copy it in the com.microsoft.Excel folder

It is possible to create or update scpt files only with VBA code, but before you can do this you must do some things manual first because Apple not allow you to create the path with VBA code. See point 3 above how to create the path below.

Library/Application Scripts/com.microsoft.Excel/

Download the example file and the scpt file

Then copy the MakeSCPTFile.scpt file from the download above inside the com.microsoft.Excel folder.

You must do this only one time and after that you can do everything with VBA code.

Copy the excel workbook on your desktop and test the code and see if it create a new scpt file inside the com.microsoft.Excel folder for you, you see that you create the script also in the VBA macro. If you change the string in the macro and run the code again it will overwrite the existing scpt file, this way you can update the scpt file.