Difficulties with AppleScript automation

Hello All,

I am currently in development of an AppleScript that reads identifying numbers, dates, and start and end times from an Excel workbook and creates events in Apple Calendar with said information.

I have a working script that does what I need, provided the relevant Excel workbook is already open, however trying to automate this script has proven to be difficult. (This code assumes column C of sheet titled ‘Storage’ stores identifying number, column D stores date, E stores start time and F stores end time)

My first idea was to create a script within Excel VBA using the ‘AppleScriptTask’ function that would ‘call’ my AppleScript from within the workbook when run. I could not get this to work even with a greatly simplified code. I ensured that my script was saved in ‘~/Library/Application Scripts/com.microsoft.Excel/’ but still no luck, I kept running into ‘Run-time error ‘5’: Invalid procedure call or argument’.

My second idea was to export my AppleScript to an application and simply run it from my desktop or dock while the relevant workbook was open, however this too proved to be difficult. Once exported, running my new application prompted a few permission requests that I was able to allow. The app then gets stuck in a recursive loop with a pop-up window asking for permission to add events to Apple Calendar. If I click ‘Allow’ on this window another the exact same pops up in its place. If I click ‘Do Not Allow’ a new window pops up stating I don’t have permission to add events to AppleCalendar and the app stops running. [Note: I went through settings and for my application I manually allowed permissions for automation, accessibility, files and folders, calendar, and full disk access. This made no difference in outcome.]

I am very new to AppleScript development and have used GPT and other AI’s to help me with development so far, but I can’t seem to get past the mentioned roadblocks.

I am running macOS Sonoma 14.1, ScriptEditor 2.11, Apple Calendar 14.0, and Excel for Mac 16.83

Below is my functional AppleScript

property workbookName : "YourWorkbookName.xlsm"

tell application "Microsoft Excel"
	activate
	set workbookOpened to false
	
	-- Check if the workbook is already open
	try
		set theWorkbook to workbook workbookName
		set workbookOpened to true
	on error
		display dialog "YourWorkbookName.xlsm is not open, please ensure the workbook is open and try again." buttons {"OK"} default button 1
		return
	end try
	
	-- If workbookOpened is true, the workbook is open
	if workbookOpened then
		set theSheet to worksheet "Storage" of theWorkbook
		set lastRow to get count of rows of used range of theSheet
		set deliveryList to {}
		
		repeat with i from 3 to lastRow
			set poNumber to value of cell ("C" & i) of theSheet as text
			set deliveryDateText to value of cell ("D" & i) of theSheet as text
			set startTimeDecimal to value of cell ("E" & i) of theSheet
			set endTimeDecimal to value of cell ("F" & i) of theSheet
			
			if poNumber ends with ".0" then set poNumber to text 1 thru -3 of poNumber
			if poNumber is not "" and deliveryDateText is not "" and startTimeDecimal is not "" and endTimeDecimal is not "" then
				set end of deliveryList to {poNumber:poNumber, date:deliveryDateText, startTime:startTimeDecimal, endTime:endTimeDecimal}
			end if
		end repeat
	end if
end tell

tell application "Calendar"
	set myCalendar to calendar "YourCalendarName"
	if myCalendar is missing value then
		display dialog "Specified calendar not found."
		return
	end if
	
	repeat with aDelivery in deliveryList
		set poNumber to poNumber of aDelivery
		set deliveryDateText to date of aDelivery
		set startTimeDecimal to startTime of aDelivery
		set endTimeDecimal to endTime of aDelivery
		set eventTitle to "PO# " & poNumber & " Delivery"
		
		-- Convert dateText and decimal times to date objects
		set deliveryDate to my formatDate(deliveryDateText)
		set startDateTime to my convertToDateTime(deliveryDate, startTimeDecimal)
		set endDateTime to my convertToDateTime(deliveryDate, endTimeDecimal)
		
		-- Check for existing events to avoid duplicates
		set sameDayEvents to (every event of myCalendar where its summary contains eventTitle and its start date ≥ deliveryDate and its start date < (deliveryDate + (1 * days)))
		if (count of sameDayEvents) is equal to 0 then
			tell myCalendar
				set newEvent to make new event with properties {summary:eventTitle, start date:startDateTime, end date:endDateTime, allday event:false}
				tell newEvent
					-- Add an alarm to the event for 1 day before
					make new display alarm at end with properties {trigger interval:-1440} -- -1440 minutes for 1 day before
				end tell
			end tell
		else
			display notification "An event for " & eventTitle & " on " & deliveryDate & " already exists. Skipping."
		end if
	end repeat
end tell

-- Helper functions for date conversion
on formatDate(excelDateText)
	try
		return date excelDateText
	on error
		display dialog "Failed to convert date: " & excelDateText
		return current date
	end try
end formatDate

on convertToDateTime(eventDate, decimalTime)
	set totalMinutes to decimalTime * 1440
	set hours to totalMinutes div 60
	set minutes to totalMinutes mod 60
	
	set adjustedDateTime to current date
	tell adjustedDateTime
		set its year to year of eventDate
		set its month to month of eventDate
		set its day to day of eventDate
		set its hours to hours
		set its minutes to minutes
		set its seconds to 0
	end tell
	
	return adjustedDateTime
end convertToDateTime

Any help, insight, or feedback is greatly appreciated, I’ve been having a blast working on this and learning about AppleScript and its capabilities, but I’m just stuck on how to proceed. Apologies if I’ve formatted anything incorrectly or overlooked anything obvious.

Thanks in advance,

-BadNoviceCoder

Can you post a copy of your spreadsheet so we can test it?

How can I post a copy? I don’t think this forum supports .xlsm file sharing. When I navigate to where it’s stored from the upload menu it is greyed out.

put it in a zip file. (i.e a compressed file/folder)

Perfect, thank you. Spreadsheet attached below.

SampleTracker.xlsm.zip (45.1 KB)

OK. Just a cursory test so far. I cleaned up the Calendar portion of the script by moving many of the commands out of the tell block that aren’t actually processed by tell application “Calendar”.
By doing so, you don’t need to use the keyword ‘my’ when calling handlers.
Also workbookOpened variable is not needed. The try block already takes into account if not opened

property workbookName : "SampleTracker.xlsm"

local theWorkbook, theSheet, lastRow, deliveryList, poNumber, deliveryDateText, startTimeDecimal, endTimeDecimal, myCalendar, aDelivery

tell application "Microsoft Excel"
	activate
	-- Check if the workbook is already open
	try
		set theWorkbook to workbook workbookName
	on error
		display dialog workbookName & " is not open, please ensure the workbook is open and try again." buttons {"OK"} default button 1
		return
	end try
	
	-- If you got here, the workbook is open
	set theSheet to worksheet "Storage" of theWorkbook
	used range of theSheet
	set lastRow to count rows of used range of theSheet
	set deliveryList to {}
	
	repeat with i from 3 to lastRow
		set poNumber to value of cell ("C" & i) of theSheet as text
		if poNumber ≠ "" then
			set deliveryDateText to value of cell ("D" & i) of theSheet as text
			set startTimeDecimal to value of cell ("E" & i) of theSheet
			set endTimeDecimal to value of cell ("F" & i) of theSheet
			
			if poNumber ends with ".0" then set poNumber to text 1 thru -3 of poNumber
			if deliveryDateText ≠ "" and startTimeDecimal ≠ "" and endTimeDecimal ≠ "" then
				set end of deliveryList to {poNumber:poNumber, date:deliveryDateText, startTime:startTimeDecimal, endTime:endTimeDecimal}
			end if
		end if
	end repeat
end tell

tell application "Calendar" to set myCalendar to calendar "Excel"
if myCalendar is missing value then
	display dialog "Specified calendar not found."
	return
end if

repeat with aDelivery in deliveryList
	set poNumber to poNumber of aDelivery
	set deliveryDateText to date of aDelivery
	set startTimeDecimal to startTime of aDelivery
	set endTimeDecimal to endTime of aDelivery
	set eventTitle to "PO# " & poNumber & " Delivery"
	
	-- Convert dateText and decimal times to date objects
	set deliveryDate to formatDate(deliveryDateText)
	set startDateTime to convertToDateTime(deliveryDate, startTimeDecimal)
	set endDateTime to convertToDateTime(deliveryDate, endTimeDecimal)
	
	tell application "Calendar"
		-- Check for existing events to avoid duplicates
		set sameDayEvents to (every event of myCalendar where its summary contains eventTitle and its start date ≥ deliveryDate and its start date < (deliveryDate + (1 * days)))
		if (count sameDayEvents) = 0 then
			tell myCalendar
				set newEvent to make new event with properties {summary:eventTitle, start date:startDateTime, end date:endDateTime, allday event:false}
				tell newEvent
					-- Add an alarm to the event for 1 day before
					make new display alarm at end with properties {trigger interval:-1440} -- -1440 minutes for 1 day before
				end tell
			end tell
		else
			display notification "An event for " & eventTitle & " on " & deliveryDate & " already exists. Skipping."
		end if
	end tell
end repeat

Just addressing the opening part but out of curiosity, why not just open the workbook? Nothing untoward will happen if it’s already open.

If you run this first snippet with the workbook open and frontmost, it will return the ‘full name’ which is the HFS path to the file.

tell application "Microsoft Excel"
	set fn to full name of workbook 1
	-->  "MacHD:Users:username:Desktop:YourWorkbookName.xlsx"
end tell

You can then use that as the default. Note that you can assign the open workbook to a variable as part of the open command. The activate object command brings it to the front in case it was already open but not in front.

tell application "Microsoft Excel"
	activate
		set ywn to open workbook workbook file name "MacHD:Users:username:Desktop:YourWorkbookName.xlsx"
	
	-- activate object workbook "YourWorkbookName.xlsx" 
	activate object ywn	
end tell

If the script needs to be run on various computers, then you can begin it with a choose file to get the local path to the workbook.

use scripting additions
set fn to (choose file) as text
--> "MacHD:Users:username:Desktop:YourWorkbookName.xlsx"

I’m running an old OS so I cannot assist with any of the security issues but here are two things I’ve read.

  • In another post here, there is a simple script that may help with sandboxing issues. It uses Word but can likely be modified to work with Excel as well.
  • Here is a post on another site from a long-time Excel MVP that has some steps to follow when trying to get Excel 2016 working and specifically mentions the error 5. The steps are straightforward to follow.

Maybe your permissions problems would go away if you ran your script from the Script menu. Find the control for showing/hiding that menu in the Script Editor’s Settings…